Files
youlegames/codes/agent/game/api/source/apis/transfer.php
2026-03-15 01:27:05 +08:00

1788 lines
67 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Created by PhpStorm.
* User: bahamut
* Date: 2018/5/21
* Time: 10:13
*/
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, PATCH, DELETE");
header("Access-Control-Allow-Headers: Content-Type, Authorization, Content-Length, X-Requested-With");
header("Access-Control-Allow-Credentials: true");
header("Content-Type: text/html; charset=utf-8");
use phprs\util\Verify;
use phprs\util\exceptions\Forbidden;
use phprs\util\Logger;
use phprs\util\exceptions\NotFound;
use phprs\ezsql\Sql;
use phprs\util\exceptions\BadRequest;
require_once 'apiBase.php';
require_once dirname(dirname(__DIR__)) . '/payment/alipay/wappay/service/AlipayTradeService.php';
require_once dirname(dirname(__DIR__)) . '/payment/alipay/wappay/buildermodel/AlipayTradeWapPayContentBuilder.php';
require_once dirname(dirname(__DIR__)) . '/payment/alipay/wappay/buildermodel/alipaytraderefundcontentbuilder.php';
require_once dirname(dirname(__DIR__)) . '/payment/alipay/f2fpay/service/AlipayTradeService.php';
require_once dirname(dirname(__DIR__)) . '/payment/alipay/f2fpay/model/builder/AlipayTradePrecreateContentBuilder.php';
require_once dirname(dirname(__DIR__)) . '/payment/alipay/f2fpay/model/builder/alipaytraderefundcontentbuilder.php';
//require_once dirname(dirname(__DIR__)) . '/payment/alipay/aop/aopclient.php';
require_once dirname(dirname(__DIR__)) . '/payment/alipay/lib/alipay_submit.class.php';
require_once dirname(dirname(__DIR__)) . '/payment/alipay/lib/alipay_notify.class.php';
/// 批量付款(银联)
define('TRANSFERCODE_UNIONPAY', 1);
/// 批量付款(支付宝)
define('TRANSFERCODE_ALIPAY', 2);
/// 批量付款(微信)
define('TRANSFERCODE_WECHAT', 3);
/// 批量付款(汇付宝)
define('TRANSFERCODE_HEEPAY', 4);
/// 批付状态(未支付)
define('TRANSFERSTATUS_NORMAL', 0);
/// 批付状态(已成功)
define('TRANSFERSTATUS_SUCCESS', 1);
/// 批付状态(已取消)
define('TRANSFERSTATUS_CANCEL', -1);
/// 通知结果(成功)
define('NOTIFYSTATUS_SUCCESS', 'success');
define('NOTIFYSTATUS_ERROR', 'error');
define('NOTIFYSTATUS_FAIL', 'fail');
class CryptBase
{
const PKCS_PAD_5 = 8;
const PKCS_PAD_7 = 32;
/**
* @param string $data 需要pkcs填充的字符串
* @param int $blocksize 填充方式,块大小
* @return string 填充的结果
**/
protected function pkcs_pad($data, $blocksize)
{
$pad = $blocksize - (strlen($data) % $blocksize);
$data .= str_repeat(chr($pad), $pad);
$data_len = strlen($data);
if ($data_len % $blocksize)
$data = str_pad($data, $data_len + $blocksize - $data_len % $blocksize, "\0");
return $data;
}
/**
* @param string $data 需要pkcs你填充的字符串
* @return string 解码的结果
*/
protected function pkcs_unpad($data)
{
$pad = ord($data[strlen($data)-1]); // 🚨 PHP8兼容性$data{x} → $data[x]
if ($pad > strlen($data))
return null;
if (strspn($data, chr($pad), strlen($data) - $pad) != $pad)
return null;
return substr($data, 0, -1 * $pad);
}
/**
* @param string $data 需要pkcs5填充的字符串
* @return string 填充的结果
**/
protected function pkcs5_pad($data)
{
return $this->pkcs_pad($data, self::PKCS_PAD_5);
}
/**
* @param string $data 需要pkcs5你填充的字符串
* @return string 解码的结果
*/
protected function pkcs5_unpad($data)
{
return $this->pkcs_unpad($data);
}
/**
* @param string $data 要pkcs7填充的数据
* @return string 填充结果
*/
protected function pkcs7_pad($data)
{
return $this->pkcs_pad($data, self::PKCS_PAD_7);
}
/**
* @param string $data 需要pkcs7你填充的字符串
* @return string 解码的结果
*/
protected function pkcs7_unpad($data)
{
return $this->pkcs_unpad($data);
}
protected function ToHex($String)
{
$Result = '';
for ($Index = 0; $Index < strlen($String); $Index++)
{
$Hex = dechex(ord($String[$Index]));
if (strlen($Hex) == 1)
$Result .= '0' . $Hex;
else
$Result .= $Hex;
}
return strtoupper($Result);
}
protected function ToStr($HexString)
{
$Result = '';
for ($Index = 0; $Index < strlen($HexString) - 1; $Index += 2)
$Result .= chr(hexdec($HexString[$Index] . $HexString[$Index + 1]));
return $Result;
}
}
class CryptOpenSSL extends CryptBase
{
/** @var string $EncryptKey 加密秘钥 */
protected $EncryptKey;
/** @var string $InitializationVector 加密向量 */
protected $InitializationVector;
public function __construct($Key = '', $IV = '')
{
$this->SetKey($Key);
$this->SetIV($IV);
}
public function __destruct()
{
$this->SetKey(null);
$this->SetIV(null);
}
protected function EncryptWithPKCSPad($String, $Method, $PKCSPadProc)
{
if (!method_exists($this, $PKCSPadProc))
return null;
$String = call_user_func_array(array(&$this, $PKCSPadProc), array($String));
$Result = openssl_encrypt($String, $Method, $this->EncryptKey, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $this->InitializationVector);
//$Result = openssl_encrypt($String, $Method, $this->EncryptKey, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING);
//return StrToUpper(Bin2Hex($Result));
return $this->ToHex($Result);
}
protected function DecryptWithPKCSPad($String, $Method, $UNPKCSPadProc)
{
if (!method_exists($this, $UNPKCSPadProc))
return null;
$Result = openssl_decrypt($this->ToStr($String), $Method, $this->EncryptKey, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $this->InitializationVector);
//$Result = openssl_decrypt ($this->ToStr($String), 'DES-EDE3', $this->EncryptKey, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING);
return call_user_func_array(array(&$this, $UNPKCSPadProc), array($Result));
}
public function GetKey()
{
return $this->EncryptKey;
}
public function SetKey($Key)
{
$this->EncryptKey = strval($Key);
}
public function GetIV()
{
return $this->InitializationVector;
}
public function SetIV($IV)
{
$this->InitializationVector = strval($IV);
}
}
class Crypt3Des extends CryptBase
{
public $key = '';
/*构造方法*/
public function encrypt($input)
{
// 数据加密
if (empty($input))
return null;
// 🚨 PHP8兼容性mcrypt_get_block_size替换为固定值保持原有行为
$size = 8; // 3DES块大小等价于 mcrypt_get_block_size(MCRYPT_3DES, 'ecb')
//$input = $this->pkcs5_pad($input, $size);
$input = $this->pkcs_pad($input, $size);
$key = str_pad($this->key, 24, '0');
// 🚨 PHP8兼容性mcrypt替换为OpenSSL严格保持原有行为
// ⚠️ 重要保持自定义双重填充算法不使用标准PKCS
// ⚠️ 重要:保持编码不匹配"缺陷" - encrypt返回Hexdecrypt期望Base64
$data = openssl_encrypt($input, 'des-ede3', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
// $data = base64_encode ( $data );
//return $data;
return $this->ToHex($data); // ⚠️ 保持Hex输出格式保持编码不匹配"缺陷"
}
public function decrypt($encrypted)
{
// 数据解密
if (!$encrypted || empty($encrypted))
return null;
$encrypted = base64_decode($encrypted); // ⚠️ 保持期望Base64输入的"缺陷"
if (!$encrypted || empty($encrypted))
return null;
$key = str_pad($this->key, 24, '0');
// 🚨 PHP8兼容性mcrypt替换为OpenSSL严格保持原有行为
// ⚠️ 重要保持与encrypt()编码不匹配的"缺陷" - encrypt返回Hex但decrypt期望Base64
$decrypted = openssl_decrypt($encrypted, 'des-ede3', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
$y = $this->pkcs5_unpad($decrypted); // ⚠️ 保持pkcs5_unpad逻辑
return $y;
}
}
class TripleDES extends CryptOpenSSL
{
// /**
// * @param string $String 要加密的字符串
// * @return string 加密的结果
// */
// public function Encrypt($String)
// {
// $String = $this->pkcs5_pad($String);
// if (strlen($String) % 8)
// $String = str_pad($String, strlen($String) + 8 - strlen($String) % 8, "\0");
//
// //$Result = openssl_encrypt($String, 'DES-EDE3', $this->EncryptKey, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $this->InitializationVector);
// $Result = openssl_encrypt($String, 'DES-EDE3', $this->EncryptKey, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING);
// //return StrToUpper(Bin2Hex($Result));
// return $this->ToHex($Result);
// }
//
//
// /**
// * @param string $String 要解密的字符串
// * @return string 解密的结果
// */
// public function Decrypt($String)
// {
// //$Result = openssl_decrypt ($this->ToStr($String), 'DES-EDE3', $this->EncryptKey, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $this->InitializationVector);
// $Result = openssl_decrypt ($this->ToStr($String), 'DES-EDE3', $this->EncryptKey, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING);
// return $this->pkcs5_unpad($Result);
// }
public function __construct($Key)
{
parent::__construct($Key, '');
}
public function Encrypt($String)
{
return $this->EncryptWithPKCSPad($String, 'DES-EDE3', 'pkcs5_pad');
}
public function Decrypt($String)
{
return $this->DecryptWithPKCSPad($String, 'DES-EDE3', 'pkcs5_unpad');
}
}
class AesCrypt extends CryptOpenSSL
{
/**
* [encrypt description]
* 使用mcrypt库进行加密
* @param string $input
* @param string $key
* @return string
*/
public function mcryptEncrypt($input, $key)
{
// 🚨 PHP8兼容性mcrypt_get_block_size替换为固定值保持原有行为
$size = 16; // AES-128块大小等价于 mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB)
//$input = $this->pkcs5_pad($input, $size);
$input = $this->pkcs_pad($input, $size); // ⚠️ 保持自定义双重填充不使用标准PKCS
// 🚨 PHP8兼容性mcrypt替换为OpenSSL严格保持原有行为
// ⚠️ 重要AES-128-ECB模式保持双重填充算法
$data = openssl_encrypt($input, 'AES-128-ECB', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
$data = base64_encode($data); // ⚠️ 保持Base64输出格式
return $data;
}
/**
* [decrypt description]
* 使用mcrypt库进行解密
* @param string $sStr
* @param string $sKey
* @return string
*/
public function mcryptDecrypt($sStr, $sKey)
{
// 🚨 PHP8兼容性mcrypt替换为OpenSSL严格保持原有行为
// ⚠️ 重要保持自定义去填充逻辑与标准PKCS不同的处理
$decrypted = openssl_decrypt(base64_decode($sStr), 'AES-128-ECB', $sKey, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
// ⚠️ 保持原有自定义去填充逻辑直接截取padding长度
$dec_s = strlen($decrypted);
$padding = ord($decrypted[$dec_s-1]); // 🚨 PHP8兼容性$data{x} → $data[x]
$decrypted = substr($decrypted, 0, -$padding);
return $decrypted;
}
/**
* [opensslDecrypt description]
* 使用openssl库进行加密
* @param string $sStr
* @param string $sKey
* @param string $method
* @return string
*/
public function opensslEncrypt($sStr, $sKey, $method = 'AES-128-ECB')
{
$str = openssl_encrypt($sStr, $method, $sKey);
return $str;
}
/**
* [opensslDecrypt description]
* 使用openssl库进行解密
* @param string $sStr
* @param string $sKey
* @param string $method
* @return string
*/
public function opensslDecrypt($sStr, $sKey, $method = 'AES-128-ECB')
{
$str = openssl_decrypt($sStr,$method,$sKey);
return $str;
}
public function Encrypt($String)
{
return $this->EncryptWithPKCSPad($String, 'AES-128-CBC', 'pkcs7_pad');
}
public function Decrypt($String)
{
return $this->DecryptWithPKCSPad($String, 'AES-128-CBC', 'pkcs7_unpad');
}
}
class AesCryptTool extends AesCrypt
{
public function GetKey()
{
return base64_encode(parent::GetKey());
}
public function SetKey($Key)
{
parent::SetKey(base64_decode($Key));
}
public function GetIV()
{
return base64_encode(parent::GetIV());
}
public function SetIV($IV)
{
parent::SetIV(base64_decode($IV));
}
public function ToHex($String)
{
return base64_encode($String);
}
public function ToStr($HexString)
{
return base64_decode($HexString);
}
}
abstract class Account
{
public function __construct($parameter = null)
{
if (!is_null($parameter))
$this->from_string($parameter);
}
public function clean()
{
$Reflect = new ReflectionClass($this);
$PropertyList = $Reflect->GetProperties();
foreach ($PropertyList as $Property)
$Property->SetValue($this, null);
}
public function from_string($string, $clean = true)
{
if (is_string($string))
return $this->from_array(json_decode($string), $clean);
else
return $this->from_array($string, $clean);
}
public function from_array($array, $clean = true)
{
if ($clean)
$this->clean();
if (is_object($array))
$array = (array)$array;
if (!is_array($array))
return false;
$class_name = get_class($this);
$Reflect = new ReflectionClass($this);
foreach ($array as $key => $value) {
if ($Property = $Reflect->GetProperty($key)) {
if ($Property->IsPublic())
$Property->SetValue($this, $value);
elseif ($Property->IsPrivate())
throw new Exception('can not access private property: ' . $class_name . '::' . $key . ' in ' . __FILE__ . ' on line ' . __LINE__);
elseif ($Property->IsProtected())
throw new Exception('can not access protected property: ' . $class_name . '::' . $key . ' in ' . __FILE__ . ' on line ' . __LINE__);
else
throw new Exception('can not access unknown property: ' . $class_name . '::' . $key . ' in ' . __FILE__ . ' on line ' . __LINE__);
$Property = null;
} else
throw new Exception('undefined property: ' . $class_name . '::' . $key . ' in ' . __FILE__ . ' on line ' . __LINE__);
}
return true;
}
public function to_string()
{
return JsonObjectToJsonString($this);
}
public function to_xml()
{
return XmlObjectToXmlString($this);
}
public function to_array()
{
return (array)$this;
}
}
class Account_Alipay extends Account
{
/** @var string $out_biz_no */
//public $out_biz_no; /// 必选 商户转账唯一订单号。发起转账来源方定义的转账单据ID用于将转账回执通知给来源方。不同来源方给出的ID可以重复同一个来源方必须保证其ID的唯一性。只支持半角英文、数字及“-”、“_”。
/** @var string $payee_type */
//public $payee_type; /// 必选 收款方账户类型。可取值1、ALIPAY_USERID支付宝账号对应的支付宝唯一用户号。以2088开头的16位纯数字组成。2、ALIPAY_LOGONID支付宝登录号支持邮箱和手机号格式。
/** @var string $payee_account */
//public $payee_account; /// 必选 收款方账户。与payee_type配合使用。付款方和收款方不能是同一个账户。
/** @var string $amount */
//public $amount; /// 必选 转账金额,单位:元。 只支持2位小数小数点前最大支持13位金额必须大于等于0.1元。最大转账金额以实际签约的限额为准。
/** @var string $payer_show_name */
//public $payer_show_name; /// 可选 付款方姓名最长支持100个英文/50个汉字。显示在收款方的账单详情页。如果该字段不传则默认显示付款方的支付宝认证姓名或单位名称。
/** @var string $payee_real_name */
//public $payee_real_name; /// 可选 收款方真实姓名最长支持100个英文/50个汉字。如果本参数不为空则会校验该账户在支付宝登记的实名是否与收款方真实姓名一致。
/** @var string $remark */
//public $remark; /// 可选 转账备注支持200个英文/100个汉字。当付款方为企业账户且转账金额达到大于等于50000元remark不能为空。收款方可见会展示在收款用户的收支详情中。
/** @var index $id */
public $id; /// 流水号
/** @var string $ret_code */
public $ret_code; /// 返回值
/** @var string $ret_info */
public $ret_info; /// 返回消息
/** @var string $account_no */
public $account_no; /// 收款账户
/** @var string $account_name */
public $account_name; /// 收款人姓名
/** @var float $money */
public $money; /// 转账金额
/** @var string $title */
public $title; /// 显示在收款方的账单详情页
/** @var string $remark */
public $remark; /// 转账备注
/** @var string $order_id */
public $order_id; /// 支付订单号
/** @var string $pay_time */
public $pay_time; /// 支付时间
}
class Account_Wechat extends Account
{
/** @var string $openid */
public $openid; /// openid
/** @var string $unionid */
public $unionid; /// unionid
/** @var string $trade_no */
public $trade_no; /// 订单号
/** @var float $money */
public $money; /// 转账金额
/** @var string $title */
public $title; /// 显示在收款方的账单详情页
/** @var string $remark */
public $remark; /// 转账备注
/** @var string $order_id */
public $order_id; /// 支付订单号
/** @var string $pay_time */
public $pay_time; /// 支付时间
}
class Account_HeePay extends Account
{
/// 商户流水号^银行编号^对公对私^收款人帐号^收款人姓名^付款金额^付款理由^省份^城市^收款支行名称
/** @var int $mch_no 商户流水号 */
public $mch_no;
/** @var int $bank_id 银行编号 */
public $bank_id;
/** @var int $type 对公对私(0:对私账户; 1:对公账户) */
public $type;
/** @var string $account_no 收款人帐号 */
public $account_no;
/** @var string $account_name 收款人姓名 */
public $account_name;
/** @var float $money 付款金额 */
public $money;
/** @var string $reason 付款理由 */
public $reason;
/** @var string $status 付款状态(-1=无效0=未处理1=成功) */
public $status;
/** @var string $province 省份 */
public $province;
/** @var string $city 城市 */
public $city;
/** @var string $bank_name 收款支行名称 */
public $bank_name;
}
/**
*
* 支付管理
* @path("/transfer")
*/
class Transfer extends apiBase
{
/**
* 获取门店批量付款方式列表
* @route({"POST","/querylist"})
* @param({"appid","$._POST.appid"}) 应用appid
* @param({"devkey","$._POST.devkey"}) 开发者key
* @param({"market_key","$._POST.market_key"}) 门店key
* @param({"version","$._POST.version"}) 版本号
* @param({"sign","$._POST.sign"}) 签名
*
* @throws({"phprs\util\exceptions\Forbidden","res", "403 Forbidden",{"error":"Forbidden"}}) cookie不可用
* @return string|returnObject|mixed
*/
public function querylist($appid = null, $devkey = null, $market_key = null, $version = null, $sign = null)
{
/// 验证公共参数是否合法
$this->init($appid, $devkey);
$verify_result = $this->verify_admin($market_key);
if (is_error_api($verify_result))
{
if ($verify_result instanceof returnObject)
return $verify_result;
$return = new returnObject();
$return->from_array((array)$verify_result);
return $verify_result;
}
switch ($version)
{
case 1:
{
if (empty($market_key))
{
$data = Sql::select('type_id, type_key, type_name, image')
->from('syweb_transfertype_base')
->where('is_enabled = 1')
->get($this->db);
return new returnObject(0, 0, '', $data);
}
else
{
$data = Sql::select('a.type_id, a.type_key, a.type_name, a.image')
->from('syweb_transfertype_base a, syweb_transfertype_market b')
->where('a.type_key = b.type_key and a.is_enabled = 1 and b.market_key = ?', $market_key)
->get($this->db);
return new returnObject(0, 0, '', $data);
}
break;
}
default:
return new returnObject(500, -1, "不支持的接口版本:{$version}");
}
}
/**
* 获取门店对应银行代码
* @route({"POST","/querybanklist"})
* @param({"appid","$._POST.appid"}) 应用appid
* @param({"devkey","$._POST.devkey"}) 开发者key
* @param({"market_key","$._POST.market_key"}) 门店key
* @param({"version","$._POST.version"}) 版本号
* @param({"sign","$._POST.sign"}) 签名
*
* @throws({"phprs\util\exceptions\Forbidden","res", "403 Forbidden",{"error":"Forbidden"}}) cookie不可用
* @return string|returnObject|mixed
*/
public function querybanklist($appid = null, $devkey = null, $market_key = null, $version = null, $sign = null)
{
/// 验证公共参数是否合法
$this->init($appid, $devkey);
$verify_result = $this->verify_admin($market_key);
if (is_error_api($verify_result))
{
if ($verify_result instanceof returnObject)
return $verify_result;
$return = new returnObject();
$return->from_array((array)$verify_result);
return $verify_result;
}
switch($version)
{
case 1:
$result = array(
array('code' => 1, 'name' => '中国工商银行', ),
array('code' => 2, 'name' => '建设银行', ),
array('code' => 3, 'name' => '中国农业银行', ),
array('code' => 4, 'name' => '中国邮政储蓄银行', ),
array('code' => 5, 'name' => '中国银行', ),
array('code' => 6, 'name' => '交通银行', ),
array('code' => 7, 'name' => '招商银行', ),
array('code' => 8, 'name' => '光大银行', ),
array('code' => 9, 'name' => '浦发银行', ),
array('code' => 10, 'name' => '华夏银行', ),
array('code' => 11, 'name' => '广东发展银行', ),
array('code' => 12, 'name' => '中信银行', ),
array('code' => 13, 'name' => '兴业银行', ),
array('code' => 14, 'name' => '民生银行', ),
array('code' => 15, 'name' => '杭州银行', ),
array('code' => 16, 'name' => '上海银行', ),
array('code' => 17, 'name' => '宁波银行', ),
array('code' => 18, 'name' => '平安银行', ),
array('code' => 23, 'name' => '渤海银行', ),
array('code' => 25, 'name' => '徽商银行', ),
array('code' => 26, 'name' => '江苏银行', ),
array('code' => 32, 'name' => '浙商银行', ),
array('code' => 33, 'name' => '北京银行', ),
array('code' => 36, 'name' => '潍坊银行', ),
array('code' => 38, 'name' => '浙江泰隆商业银行', ),
array('code' => 39, 'name' => '济宁银行', ),
array('code' => 40, 'name' => '台州银行', ),
array('code' => 41, 'name' => '汉口银行', ),
array('code' => 42, 'name' => '安徽省农村信用社联合社', ),
array('code' => 43, 'name' => '郑州银行', ),
array('code' => 44, 'name' => '中原银行', ),
array('code' => 45, 'name' => '宜宾商业银行', ),
array('code' => 46, 'name' => '莱商银行', ),
array('code' => 47, 'name' => '日照银行', ),
array('code' => 48, 'name' => '常熟农商银行', ),
array('code' => 49, 'name' => '北京农商银行', ),
array('code' => 50, 'name' => '福建省农村信用社联合社', ),
array('code' => 51, 'name' => '齐商银行', ),
array('code' => 52, 'name' => '云南省农村信用社联合社', ),
array('code' => 53, 'name' => '山东省农村信用社联合社', ),
array('code' => 54, 'name' => '广东华兴银行', ),
array('code' => 55, 'name' => '江西银行', ),
array('code' => 56, 'name' => '东营银行', ),
array('code' => 57, 'name' => '浙江稠州商业银行', ),
array('code' => 58, 'name' => '重庆农村商业银行', ),
array('code' => 59, 'name' => '晋城银行', ),
array('code' => 60, 'name' => '秦农银行', ),
array('code' => 61, 'name' => '长安银行', ),
array('code' => 62, 'name' => '成都银行', ),
array('code' => 63, 'name' => '恒丰银行', ),
array('code' => 64, 'name' => '承德银行', ),
array('code' => 65, 'name' => '绍兴银行', ),
array('code' => 66, 'name' => '广东南粤银行', ),
array('code' => 67, 'name' => '青岛银行', ),
array('code' => 68, 'name' => '江苏长江商行', ),
array('code' => 69, 'name' => '包商银行', ),
array('code' => 70, 'name' => '富滇银行', ),
array('code' => 71, 'name' => '自贡市商业银行', ),
array('code' => 72, 'name' => '湖北农信', ),
array('code' => 73, 'name' => '浙江农信', ),
array('code' => 74, 'name' => '葫芦岛银行', ),
array('code' => 75, 'name' => '昆仑银行', ),
array('code' => 76, 'name' => '苏州银行', ),
array('code' => 77, 'name' => '湖州银行', ),
array('code' => 78, 'name' => '泉州银行', ),
array('code' => 79, 'name' => '广州农村商业银行', ),
array('code' => 81, 'name' => '太仓农村商业银行', ),
array('code' => 82, 'name' => '烟台银行', ),
array('code' => 83, 'name' => '上饶银行', ),
array('code' => 84, 'name' => '绵阳市商业银行', ),
array('code' => 85, 'name' => '德州银行', ),
array('code' => 86, 'name' => '广西农村信用社', ),
array('code' => 87, 'name' => '柳州银行', ),
array('code' => 88, 'name' => '新韩银行中国', ),
array('code' => 89, 'name' => '长沙银行', ),
array('code' => 90, 'name' => '黄河农村商业银行', ),
array('code' => 91, 'name' => '鞍山银行', ),
array('code' => 92, 'name' => '龙江银行', ),
array('code' => 93, 'name' => '河北银行', ),
array('code' => 94, 'name' => '内蒙古银行', ),
array('code' => 95, 'name' => '吉林农村信用社', ),
array('code' => 96, 'name' => '浙江三门银座村镇银行', ),
array('code' => 97, 'name' => '东莞银行', ),
array('code' => 98, 'name' => '泰安银行', ),
array('code' => 99, 'name' => '桂林银行股份有限公司', ),
array('code' => 100, 'name' => '昆山农村商业银行', ),
array('code' => 101, 'name' => '攀枝花市商业银行', ),
array('code' => 102, 'name' => '西安银行', ),
array('code' => 103, 'name' => '营口银行', ),
array('code' => 104, 'name' => '江苏省农村信用社联合社', ),
array('code' => 105, 'name' => '顺德农村商业银行', ),
array('code' => 106, 'name' => '张家港农村商业银行', ),
array('code' => 107, 'name' => '重庆黔江银座村镇银行', ),
array('code' => 108, 'name' => '临商银行', ),
array('code' => 109, 'name' => '洛阳银行', ),
array('code' => 110, 'name' => '邢台银行', ),
array('code' => 111, 'name' => '韩亚银行', ),
array('code' => 112, 'name' => '广西北部湾银行', ),
array('code' => 113, 'name' => '张家口市商业银行', ),
array('code' => 114, 'name' => '珠海华润银行', ),
array('code' => 115, 'name' => '天津银行', ),
array('code' => 116, 'name' => '阜新银行', ),
array('code' => 117, 'name' => '吴江农村商业银行', ),
array('code' => 118, 'name' => '友利银行', ),
array('code' => 119, 'name' => '北京顺义银座村镇银行', ),
array('code' => 120, 'name' => '晋商银行', ),
array('code' => 121, 'name' => '赣州银行', ),
array('code' => 122, 'name' => '鄞州银行', ),
array('code' => 123, 'name' => '兰州银行', ),
array('code' => 124, 'name' => '锦州银行', ),
array('code' => 125, 'name' => '邯郸市商业银行', ),
array('code' => 126, 'name' => '深圳福田银座村镇银行', ),
array('code' => 127, 'name' => '东莞农村商业银行', ),
array('code' => 128, 'name' => '乌鲁木齐市商业银行', ),
array('code' => 129, 'name' => '浙江景宁银座村镇银行', ),
array('code' => 130, 'name' => '威海市商业银行', ),
array('code' => 131, 'name' => '海南省农村信用社', ),
array('code' => 132, 'name' => '商丘银行', ),
array('code' => 133, 'name' => '鄂尔多斯银行', ),
array('code' => 134, 'name' => '江西赣州银座村镇银行', ),
array('code' => 135, 'name' => '天津农商银行', ),
array('code' => 136, 'name' => '重庆银行', ),
array('code' => 137, 'name' => '宁夏银行', ),
array('code' => 138, 'name' => '浙江民泰商业银行', ),
array('code' => 140, 'name' => '长城华西银行', ),
array('code' => 141, 'name' => '廊坊银行', ),
array('code' => 142, 'name' => '沧州银行', ),
array('code' => 143, 'name' => '福建海峡银行', ),
array('code' => 144, 'name' => '嘉兴银行', ),
array('code' => 145, 'name' => '吉林银行', ),
array('code' => 146, 'name' => '青海银行', ),
array('code' => 147, 'name' => '重庆渝北银座村镇银行', ),
array('code' => 148, 'name' => '枣庄银行', ),
);
return new returnObject(0, 0, '', $result);
default:
return new returnObject(500, -1, "不支持的接口版本:{$version}");
}
}
/**
* 批量付款
* @route({"GET","/transfer"})
* @route({"POST","/transfer"})
* @param({"appid","$._REQUEST.appid"}) 应用appid
* @param({"devkey","$._REQUEST.devkey"}) 开发者key
* @param({"market_key","$._REQUEST.market_key"}) 门店key
* @param({"order_no","$._REQUEST.order_no"}) 订单号
* @param({"account_list", "$._REQUEST.account_list"}) 收款账户列表
* @param({"transfer_type", "$._REQUEST.transfer_type"}) 批付类型
*
* @param({"notice_url","$._REQUEST.notice_url"}) 回调地址(异步)
* @param({"return_url","$._REQUEST.return_url"}) 回调地址(同步)
*
* @param({"version","$._REQUEST.version"}) 版本号
* @param({"sign","$._REQUEST.sign"}) 签名
*
* @throws({"phprs\util\exceptions\Forbidden","res", "403 Forbidden",{"error":"Forbidden"}}) cookie不可用
* @return string|returnObject|mixed
*/
public function transfer(
$appid = null, $devkey = null, $market_key = null,
$order_no = null, $account_list = null, $transfer_type = null,
$notice_url = null, $return_url = null,
$version = null, $sign = null)
{
switch ($version)
{
case 1:
{
/// 支付前校验
$attach = $this->verify_transfer_v1($appid, $devkey, $market_key, $account_list, $notice_url, $sign);
if ($attach instanceof returnObject)
return $attach;
switch ($transfer_type)
{
case TRANSFERCODE_UNIONPAY: /// 银联代付
return $this->_transfer_unionpay_v1($order_no, $account_list, $notice_url, $attach);
case TRANSFERCODE_ALIPAY: /// 支付宝转账到账户
return $this->_transfer_alipay_v1($order_no, $account_list, $notice_url, $attach);
case TRANSFERCODE_WECHAT: /// 微信企业支付
return $this->_transfer_wechat_v1($order_no, $account_list, $notice_url, $attach);
case TRANSFERCODE_HEEPAY: /// 汇付宝批付
return $this->_transfer_heepay_v1($order_no, $account_list, $notice_url, $attach);
default:
return new returnObject(500, -1, "未知的支付类型:{$transfer_type}");
}
break;
}
default:
return new returnObject(500, -1, "不支持的接口版本:{$version}");
}
}
/**
* @note 银联代付
* @param $order_no 订单号
* @param $account_list 收款账户列表
* @param $notice_url 异步通知地址
* @param array|mixed $attach 附带的其他参数
* @return returnObject|mixed
*/
private function _transfer_unionpay_v1($order_no, $account_list, $notice_url, $attach)
{
return new returnObject(1, -1, '还没有实现该功能, 请关注平台后续更新!', null);
}
/**
* @note 支付宝转账到账户
* @param $order_no 订单号
* @param $account_list 收款账户列表{"account_no":"收款账户","account_name":"收款人姓名","money":"转账金额","title":"显示在收款方的账单详情页","remark":"转账备注"}
* @param $notice_url 异步通知地址
* @param array|mixed $attach 附带的其他参数
* @return returnObject|mixed
*/
private function _transfer_alipay_v1($order_no, $account_list, $notice_url, $attach)
{
//return new returnObject(1, -1, '还没有实现该功能, 请关注平台后续更新!', null);
$alipay_pid = $this->marketInfo['alipay_pid']; /// 支付宝pid
$alipay_key = $this->marketInfo['alipay_key']; /// 支付宝安全检验码
$alipay_account_no = $this->marketInfo['alipay_account_no']; /// 支付宝账号
$alipay_account_name = $this->marketInfo['alipay_account_name']; /// 支付宝账户名
if (is_string($account_list))
$account_list = JsonStringToJsonObject($account_list);
if (!is_array($account_list))
$account_list = array($account_list);
$account = new Account_Alipay();
$batch_fee = 0; /// 总金额
$batch_num = count($account_list); /// 总笔数
$detail_data = array();
$index = 0;
$acc_list = array();
foreach ($account_list as $item)
{
$account->from_string($item);
$account->id = ++$index;
$account->ret_code = 0;
$account->ret_info = 'wait';
$batch_fee += $account->money; /// 金额
array_push($detail_data, "{$account->id}^{$account->account_no}^{$account->account_name}^{$account->money}^{$account->remark}");
array_push($acc_list, $account->to_array());
}
$transfer = $this->saved_transfer_info_v1($order_no, $acc_list, $notice_url, TRANSFERCODE_ALIPAY, $attach);
if (!$transfer)
return new returnObject(1, 500, '写入支付信息失败, 请联系管理员或稍候再试!', null);
$data = array(
'service' => 'batch_trans_notify',
'partner' => $alipay_pid,
'notify_url' => $this->getFullUrl('/api/transfer/notify/alipay'), /// 通知地址
'email' => $alipay_account_no, /// 付款方账号
'account_name' => $alipay_account_name, /// 付款方账户名 个人支付宝账号是真实姓名公司支付宝账号是公司名称
'pay_date' => date('Ymd'), /// 付款当天日期 格式:年[4位]月[2位]日[2位]20100801
'batch_no' => sprintf('%s%08d', date('YmdHis'), $transfer['id']), /// 批次号 格式:当天日期[8位]+序列号[3至16位]201008010000001
'batch_fee' => $batch_fee, /// 付款总金额 即参数detail_data的值中所有金额的总和
'batch_num' => $batch_num, /// 付款笔数 即参数detail_data的值中“|”字符出现的数量加1最大支持1000笔即“|”字符出现的数量999个
'detail_data' => implode('|', $detail_data), /// 付款详细数据 格式流水号1^收款方帐号1^真实姓名^付款金额1^备注说明1|流水号2^收款方帐号2^真实姓名^付款金额2^备注说明2....
'_input_charset' => trim(strtolower('utf-8')), /// 字符编码格式 目前支持 gbk 或 utf-8
);
/// 建立请求
$alipaySubmit = new AlipaySubmit(array(
'partner' => $alipay_pid, /// pid
'md5_key' => $alipay_key, /// 支付宝安全检验码
'sign_type' => 'MD5', /// 签名方式
'input_charset' => 'utf-8', /// 字符编码格式 目前支持 gbk 或 utf-8
));
$html_text = $alipaySubmit->buildRequestForm($data, 'get', '确认');
die($html_text);
// $alipay_appid = $this->marketInfo['alipay_appid']; /// 支付宝appid
//
// $aop = new AopClient();
// $aop->gatewayUrl = 'https://openapi.alipay.com/gateway.do';
// $aop->appId = $alipay_appid; /// 支付宝分配给开发者的应用ID
// ///$aop->rsaPrivateKey = '请填写开发者私钥去头去尾去回车,一行字符串'; /// 证书私钥
// $aop->rsaPrivateKeyFilePath = dirname(dirname(__DIR__)) . '/payment/alipay/key/' . $alipay_appid . '/rsa_private_key.pem'; /// 商户私钥文件名
// ///$aop->alipayrsaPublicKey = '请填写支付宝公钥,一行字符串'; /// 证书公钥
// $aop->alipayPublicKey = dirname(dirname(__DIR__)) . '/payment/alipay/key/' . $alipay_appid . '/alipay_rsa_public_key.pem'; /// 支付宝公钥文件名
// $aop->apiVersion = '1.0'; /// 版本
// $aop->signType = 'RSA2'; /// 商户生成签名字符串所使用的签名算法类型目前支持RSA2和RSA推荐使用RSA2
// $aop->postCharset = 'GBK'; /// 请求使用的编码格式如utf-8,gbk,gb2312等
// $aop->format = 'json'; /// 仅支持JSON
//
// if (is_string($account_list))
// $account_list = JsonStringToJsonObject($account_list);
//
// $account = new Account_Alipay();
// $list = array();
// $success = 0;
// $failed = 0;
// foreach ($account_list as $item)
// {
// $account->from_string($item);
// $biz_content = array(
// 'out_biz_no' => md5(date('YmdHis') . $alipay_appid . random(8, 1)), /// 必选 商户转账唯一订单号。发起转账来源方定义的转账单据ID用于将转账回执通知给来源方。不同来源方给出的ID可以重复同一个来源方必须保证其ID的唯一性。只支持半角英文、数字及“-”、“_”。
// 'payee_type' => 'ALIPAY_LOGONID', /// 必选 收款方账户类型。可取值1、ALIPAY_USERID支付宝账号对应的支付宝唯一用户号。以2088开头的16位纯数字组成。2、ALIPAY_LOGONID支付宝登录号支持邮箱和手机号格式。
// 'payee_account' => $account->account_no, /// 必选 收款方账户。与payee_type配合使用。付款方和收款方不能是同一个账户。
// 'amount' => $account->money, /// 必选 转账金额,单位:元。 只支持2位小数小数点前最大支持13位金额必须大于等于0.1元。最大转账金额以实际签约的限额为准。
// 'payer_show_name' => $account->title, /// 可选 付款方姓名最长支持100个英文/50个汉字。显示在收款方的账单详情页。如果该字段不传则默认显示付款方的支付宝认证姓名或单位名称。
// 'payee_real_name' => $account->account_name, /// 可选 收款方真实姓名最长支持100个英文/50个汉字。如果本参数不为空则会校验该账户在支付宝登记的实名是否与收款方真实姓名一致。
// 'remark' => $account->remark, /// 可选 转账备注支持200个英文/100个汉字。当付款方为企业账户且转账金额达到大于等于50000元remark不能为空。收款方可见会展示在收款用户的收支详情中。
// );
//
// $request = new AlipayFundTransToaccountTransferRequest();
// $request->setBizContent(JsonObjectToJsonString($biz_content));
// $result = $aop->execute($request);
// $responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
// $resultCode = $result->$responseNode->code;
// if (!empty($resultCode) && $resultCode == 10000) {
// //echo "成功";
// $account->ret_code = 0;
// $account->ret_info = NOTIFYSTATUS_SUCCESS;
// $account->order_id = $result->$responseNode->order_id;
// $account->pay_time = $result->$responseNode->pay_date;
// $success++;
// } else {
// //echo "失败";
// $account->ret_code = $result->$responseNode->code;
// $account->ret_info = $result->$responseNode->msg;
// $failed++;
// }
//
// array_push($list, $account);
// }
//
// Sql::update('syweb_core_transferinfo')
// ->setArgs(array('status' => TRANSFERSTATUS_SUCCESS, 'account_list' => JsonObjectToJsonString($list), ))
// ->whereArgs(array('id' => $transfer['id']))
// ->exec($this->db);
//
// /// 发送回调通知
// $this->send_order_notice();
// return new returnObject(0, 0, "批量支付完成!成功{$success}笔,失败{$failed}笔!");
}
/**
* 作用使用证书以post方式提交xml到对应的接口url
*/
function curl_post_ssl($url, $vars, $second=30)
{
$ch = curl_init();
//超时时间
curl_setopt($ch,CURLOPT_TIMEOUT,$second);
curl_setopt($ch,CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,false);
//以下两种方式需选择一种
/******* 此处必须为文件服务器根目录绝对路径 不可使用变量代替*********/
curl_setopt($ch,CURLOPT_SSLCERT,"/home/lizi/addons/grow/template/mobile/cash/apiclient_cert.pem");
curl_setopt($ch,CURLOPT_SSLKEY,"/home/lizi/addons/grow/template/mobile/cash/apiclient_key.pem");
curl_setopt($ch,CURLOPT_POST, 1);
curl_setopt($ch,CURLOPT_POSTFIELDS,$vars);
$data = curl_exec($ch);
if ($data) {
curl_close($ch);
return $data;
} else {
$error = curl_errno($ch);
echo "call faild, errorCode:$error\n";
curl_close($ch);
return false;
}
}
/**
* @note 微信企业付款
* @param $order_no 订单号
* @param $account_list 收款账户列表 {"openid":"收款openid","unionid":"收款unionid","trade_no":"订单号","money":"转账金额","title":"显示在收款方的账单详情页","remark":"转账备注"}
* @param $notice_url 异步通知地址
* @param array|mixed $attach 附带的其他参数
* @return returnObject|mixed
*/
private function _transfer_wechat_v1($order_no, $account_list, $notice_url, $attach)
{
//return new returnObject(1, -1, '还没有实现该功能, 请关注平台后续更新!', null);
$weixin_appid = $this->marketInfo['weixin_appid']; /// 微信appid
$weixin_secret_appid = $this->marketInfo['weixin_secret_appid']; /// 微信secretid
$weixin_mchid = $this->marketInfo['weixin_mchid']; /// 微信商户号
$weixin_paykey = $this->marketInfo['weixin_paykey']; /// 微信支付key
if (is_string($account_list))
$account_list = JsonStringToJsonObject($account_list);
if (!is_array($account_list))
$account_list = array($account_list);
$account = new Account_Wechat();
$index = 0;
$acc_list = [];
foreach ($account_list as $item)
{
$account->from_string($item);
$account->id = ++$index;
$account->ret_code = 0;
$account->ret_info = 'wait';
array_push($acc_list, $account->to_array());
}
$transfer = $this->saved_transfer_info_v1($order_no, $acc_list, $notice_url, TRANSFERCODE_WECHAT, $attach);
if (!$transfer)
return new returnObject(1, 500, '写入支付信息失败, 请联系管理员或稍候再试!', null);
/// 微信付款到个人的接口
$url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers';
for ($index = 0; $index < count($acc_list); $index++)
{
$acc_list[$index]['order_id'] = md5(time() . rand(100000, 999999)); /// 商户订单号
$data = [
'mch_appid' => $weixin_appid, /// 公众账号appid
'mchid' => $weixin_mchid, /// 商户号 微信支付平台账号
///'device_info' => '', /// 设备号
'nonce_str' => md5(time() . rand(1000, 9999)), /// 随机字符串
'partner_trade_no' => $acc_list[$index]['order_id'], /// 商户订单号
'openid' => $acc_list[$index]['openid'], /// 商户appid下某用户的openid
'check_name' => 'NO_CHECK', /// 校验用户姓名选项 NO_CHECK不校验真实姓名FORCE_CHECK强校验真实姓名
///'re_user_name' => '', /// 收款用户真实姓名。如果check_name设置为FORCE_CHECK则必填用户真实姓名
'amount' => intval(floatval($acc_list[$index]['money']) * 100), /// 企业付款金额,单位为分
'desc' => $acc_list[$index]['title'], /// 企业付款操作说明信息。必填。
'spbill_create_ip' => GetClientAddress(), /// Ip地址该IP同在商户平台设置的IP白名单中的IP没有关联该IP可传用户端或者服务端的IP。
];
/// 签名
$data['sign'] = mb_strtoupper(SignParameter($data, $weixin_paykey), USEDCHARSET);
/// 转成xml字符串
$xml = XmlObjectToXmlString($data);
/// 发送请求
$ch = curl_init();
/// 超时时间
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
//以下两种方式需选择一种
/******* 此处必须为文件服务器根目录绝对路径 不可使用变量代替*********/
curl_setopt($ch, CURLOPT_SSLCERT, sprintf(dirname(dirname(__DIR__)) . '\\payment\\wechat\\cert\\%s\\apiclient_cert.pem', $weixin_mchid));
curl_setopt($ch, CURLOPT_SSLKEY, sprintf(dirname(dirname(__DIR__)) . '\\payment\\wechat\\cert\\%s\\apiclient_key.pem', $weixin_mchid));
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
$result = curl_exec($ch);
if ($result) {
curl_close($ch);
} else {
$errno = curl_errno($ch);
$errmsg = curl_error($ch);
curl_close($ch);
return new returnObject(1, $errno, $errmsg);
}
$obj = XmlStringToXmlObject($result);
if (0 != strcasecmp(strval(@$obj->return_code), 'SUCCESS')) {
$acc_list[$index]['ret_code'] = strval(@$obj->return_code);
$acc_list[$index]['ret_info'] = empty(strval(@$obj->return_msg)) ? '企业付款请求失败' : strval(@$obj->return_msg);
} elseif (0 != strcasecmp(strval(@$obj->result_code), 'SUCCESS')) {
$acc_list[$index]['ret_code'] = strval(@$obj->result_code);
$acc_list[$index]['ret_info'] = empty(strval(@$obj->err_code_des)) ? '企业付款请求失败' : strval(@$obj->err_code_des);
} else {
$acc_list[$index]['transaction_id'] = strval(@$obj->payment_no); /// 微信的订单号
$acc_list[$index]['pay_time'] = strval(@$obj->payment_time); /// 支付时间
$acc_list[$index]['ret_code'] = 1;
$acc_list[$index]['ret_info'] = 'success';
}
}
Sql::update('syweb_core_transferinfo')
->setArgs(array(
'status' => TRANSFERSTATUS_SUCCESS,
'account_list' => JsonObjectToJsonString($acc_list),
'transfer_time' => time(),
))
->whereArgs(array('id' => $transfer['id']))
->exec($this->db);
return new returnObject(0, 0, '批量付款成功!', $acc_list);
}
/**
* @note 汇付宝批量付款
* @param $order_no 订单号
* @param $account_list 收款账户列表{"mch_no":"商户流水号","bank_id":"银行编号","type":"对公对私","account_no":"收款人帐号","account_name":"收款人姓名","money":"付款金额","reason":"付款理由","province":"省份","city":"城市","bank_name":"收款支行名称"}
* @param $notice_url 异步通知地址
* @param array|mixed $attach 附带的其他参数
* @return returnObject|mixed
*/
private function _transfer_heepay_v1($order_no, $account_list, $notice_url, $attach)
{
if (is_string($account_list))
$account_list = JsonStringToJsonObject($account_list);
if (!is_array($account_list))
$account_list = array($account_list);
$total_money = 0;
$detail_list = array();
$account = new Account_HeePay();
$index = 0;
$acc_list = array();
foreach ($account_list as $item)
{
$index++;
$account->from_string($item);
/// 转账总金额
$total_money += $account->money;
/// 流水号
if (empty($account->mch_no)) $account->mch_no = $index;
/// 收款格式:商户流水号^银行编号^对公对私^收款人帐号^收款人姓名^付款金额^付款理由^省份^城市^收款支行名称
array_push($detail_list, "{$account->mch_no}^{$account->bank_id}^{$account->type}^{$account->account_no}^{$account->account_name}^{$account->money}^{$account->reason}^{$account->province}^{$account->city}^{$account->bank_name}");
array_push($acc_list, $account);
}
$transfer = $this->saved_transfer_info_v1($order_no, $acc_list, $notice_url, TRANSFERCODE_HEEPAY, $attach);
if (!$transfer)
return new returnObject(1, 500, '写入支付信息失败, 请联系管理员或稍候再试!', null);
$url_small = 'https://Pay.heepay.com/API/PayTransit/PayTransferWithSmallAll.aspx'; /// 小额正式交易地址
$url_large = 'https://Pay.heepay.com/API/PayTransit/PayTransferWithLargeWork.aspx'; /// 大额正式交易地址
$transfer_key = $this->marketInfo['heepay_transferkey']; /// 转账key
$detail_data_key = $this->marketInfo['heepay_3deskey']; /// 账户数据的3des加密key
$version = 3; /// 当前接口版本号3
$agent_id = $this->marketInfo['heepay_mchid']; /// 商户内码例如1664502
$batch_no = $transfer['out_trade_no']; /// 批量付款订单号要保证唯一。最大长度50个字符最小长度10个字符
$batch_amt = $total_money; /// 付款总金额不可为空,单位:元,小数点后保留两位。
$batch_num = count($account_list); /// 该次付款总笔数,付给多少人的数目,“单笔数据集”里面的数据总笔数
$detail_data = implode('|', $detail_list); /// 批付到银行帐户格式:“商户流水号^银行编号^对公对私^收款人帐号^收款人姓名^付款金额^付款理由^省份^城市^收款支行名称”来组织数据每条整数据间用“竖线”符号分隔商户流水号长度最长20字符。省市格式请严格按照4省市查询接口中的说明填写。注整理好信息之后需要对此数据进行3DES加密具体加密方式参考接口规则-参数规定10 3DES加密机制,银行编号和对公对私对应数值请在 接口规则-参数规定8和9
$notify_url = $this->getFullUrl('/api/transfer/notify/heepay'); /// 支付后返回的商户处理页面URL参数是以http://或https://开头的完整URL地址(后台处理)提交的url地址必须外网能访问到否则无法通知商户。
$ext_param1 = $transfer['id']; /// 商户自定义原样返回最大长度50个字符
//$sign = ''; /// MD5签名结果
/// 组织签名
$sign = "agent_id={$agent_id}&batch_amt={$batch_amt}&batch_no={$batch_no}&batch_num={$batch_num}&detail_data={$detail_data}&ext_param1={$ext_param1}&key={$transfer_key}&notify_url={$notify_url}&version={$version}";
/// 获取sign密钥
$sign = md5(strtolower($sign));
/// 初始化一个对象
if (function_exists('openssl_encrypt'))
$des = new TripleDES($detail_data_key);
else
{
$des = new Crypt3Des();
$des->key = $detail_data_key;
}
$detail_data = iconv("utf-8", "gbk//IGNORE", $detail_data);
$detail_data_des = $des->Encrypt($detail_data);
$url = $total_money < 50000 ? $url_small : $url_large;
$data = array(
'agent_id' => $agent_id,
'batch_amt' => $batch_amt,
'batch_no' => $batch_no,
'batch_num' => $batch_num,
'detail_data' => $detail_data_des,
'ext_param1' => $ext_param1,
'notify_url' => $notify_url,
'sign' => $sign,
'version' => $version,
);
$result = Characet(SendPost($url, $data));
$result = XmlStringToXmlObject($result);
if (is_object($result))
{
$result = (array)$result;
if ('0000' == $result['ret_code'])
return new returnObject(0, 0, '批量付款成功!', null);
else
return new returnObject(1, $result['ret_code'], $result['ret_msg'], null);
}
else
return new returnObject(1, 500, $result, null);
}
/**
* 批量付款回调通知
* @route({"GET","/notify/unionapy"})
* @route({"POST","/notify/unionapy"})
* @throws({"phprs\util\exceptions\Forbidden","res", "403 Forbidden",{"error":"Forbidden"}}) cookie不可用
* @return string|returnObject|mixed
*/
public function notify_unionpay()
{
}
/**
* 批量付款回调通知
* @route({"GET","/notify/alipay"})
* @route({"POST","/notify/alipay"})
* @throws({"phprs\util\exceptions\Forbidden","res", "403 Forbidden",{"error":"Forbidden"}}) cookie不可用
* @return string|returnObject|mixed
*/
public function notify_alipay()
{
try {
/// notify_time 通知的发送时间。 格式yyyy-MM-dd HH:mm:ss。
/// notify_type 通知的类型。 batch_trans_notify
/// notify_id 支付宝通知校验ID商户可以用这个流水号询问支付宝该条通知的合法性。
/// sign_type 签名方式 DSA、RSA、MD5三个值可选必须大写。
/// sign 签名 请参见本文档“附录:签名与验签”。
/// batch_no 转账批次号。
/// pay_user_id 付款的支付宝账号对应的支付宝唯一用户号。以2088开头的16位纯数字组成。
/// pay_user_name 付款账号姓名。
/// pay_account_no 付款账号。
/// success_details 批量付款中成功付款的信息。格式为:流水号^收款方账号^收款账号姓名^付款金额^成功标识(S)^成功原因(null)^支付宝内部流水号^完成时间。每条记录以“|”间隔。
/// fail_details 批量付款中未成功付款的信息。格式为:流水号^收款方账号^收款账号姓名^付款金额^失败标识(F)^失败原因^支付宝内部流水号^完成时间。每条记录以“|”间隔。
$id = @$_REQUEST['batch_no'];
if (empty($id))
die (NOTIFYSTATUS_FAIL);
$id = mb_substr($id, 14, mb_strlen($id, USEDCHARSET) - 14, USEDCHARSET);
if (empty($id))
die (NOTIFYSTATUS_FAIL);
$id = intval($id);
$transfer = Sql::select('a.id, a.app_key, a.dev_key, a.market_key, b.alipay_pid, b.alipay_key, b.signkey, a.account_list, a.notice_url, a.return_url, a.status, a.notify_status, a.version')
->from('syweb_core_transferinfo a, syweb_market b')
->where('a.market_key = b.market_key and a.id = ?', $id)
->get($this->db, null);
if (empty($transfer))
//return new returnObject(1, 10003, '找不到对应的支付信息。');
die(NOTIFYSTATUS_FAIL);
else
$transfer = $transfer[0];
$alipay_pid = $transfer['alipay_pid']; /// 支付宝pid
$alipay_key = $transfer['alipay_key']; /// 支付宝安全检验码
$version = $transfer['version']; /// 版本号
switch($version) {
case 1:
/// 计算得出通知验证结果
$notify = new AlipayNotify(array(
'partner' => $alipay_pid, /// pid
'md5_key' => $alipay_key, /// 支付宝安全检验码
'sign_type' => 'MD5', /// 签名方式
'input_charset' => 'utf-8', /// 字符编码格式 目前支持 gbk 或 utf-8
));
if ($notify->verifyNotify()) {
/// 验证成功
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// 请在这里加上商户的业务逻辑程序代
/// ——请根据您的业务逻辑来编写程序(以下代码仅作参考)——
/// 获取支付宝的通知返回参数,可参考技术文档中服务器异步通知参数列表
/// 批量付款数据中转账成功的详细信息
$succ_details = $_REQUEST['success_details'];
/// 批量付款数据中转账失败的详细信息
$fail_details = $_REQUEST['fail_details'];
///
$account_list = json_decode($transfer['account_list']); /// 保存的收款账户列表
/// 处理成功的记录
$succ_list = explode('|', $succ_details);
foreach($succ_list as $succ_item) {
/// 流水号^收款方账号^收款账号姓名^付款金额^成功标识(S)^成功原因(null)^支付宝内部流水号^完成时间
$acc = explode('^', $succ_item);
/** @var Account_Alipay $account */
foreach ($account_list as $account) {
if ($account->id == $acc[0]) {
$account->ret_code = 1;
$account->ret_info = NOTIFYSTATUS_SUCCESS;
$account->order_id = $acc[6];
$account->pay_time = $acc[7];
break;
}
}
}
/// 处理失败的记录
$fail_list = explode('|', $fail_details);
foreach($fail_list as $fail_item) {
/// 流水号^收款方账号^收款账号姓名^付款金额^失败标识(F)^失败原因^支付宝内部流水号^完成时间
$acc = explode('^', $fail_item);
/** @var Account_Alipay $account */
foreach ($account_list as $account) {
if ($account->id == $acc[0]) {
$account->ret_code = -1;
$account->ret_info = $acc[5];
$account->order_id = $acc[6];
$account->pay_time = $acc[7];
break;
}
}
}
Sql::update('syweb_core_transferinfo')
->setArgs(array(
'status' => TRANSFERSTATUS_SUCCESS,
'account_list' => JsonObjectToJsonString($account_list),
'transaction_id' => @$_REQUEST['batch_no'],
'transfer_time' => time(),
))
->whereArgs(array('id' => $id))
->exec($this->db);
/// 发送回调通知
$this->send_order_notice();
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
die(NOTIFYSTATUS_SUCCESS);
} else {
/// 验证失败
die(NOTIFYSTATUS_FAIL);
}
default:
///return new returnObject(500, -1, "不支持的接口版本:{$version}");
die(NOTIFYSTATUS_FAIL);
}
} catch (Exception $e) {
die(NOTIFYSTATUS_FAIL);
}
}
/**
* 批量付款回调通知
* @route({"GET","/notify/wechat"})
* @route({"POST","/notify/wechat"})
* @throws({"phprs\util\exceptions\Forbidden","res", "403 Forbidden",{"error":"Forbidden"}}) cookie不可用
* @return string|returnObject|mixed
*/
public function notify_wechat()
{
}
/**
* 批量付款回调通知
* @route({"GET","/notify/heepay"})
* @route({"POST","/notify/heepay"})
* @throws({"phprs\util\exceptions\Forbidden","res", "403 Forbidden",{"error":"Forbidden"}}) cookie不可用
* @return string|returnObject|mixed
*/
public function notify_heepay()
{
try {
$ret_code = $_REQUEST['ret_code']; /// 返回码值0000 表示查询成功,其他详见附录
$ret_msg = $_REQUEST['ret_msg']; /// 返回码信息提示
$agent_id = $_REQUEST['agent_id']; /// 商户编号 如1234567
$hy_bill_no = $_REQUEST['hy_bill_no']; /// 汇付宝交易号(订单号)
$status = $_REQUEST['status']; /// -1=无效0=未处理1=成功
$batch_no = $_REQUEST['batch_no']; /// 商户系统内部的订单号
$batch_amt = $_REQUEST['batch_amt']; /// 成功付款金额
$batch_num = $_REQUEST['batch_num']; /// 成功付款数量
$detail_data = $_REQUEST['detail_data']; /// 付款明细,单笔数据集里面按照“商户流水号^收款人帐号^收款人姓名^付款金额^付款状态”来组织数据每条整数据间用“竖线”符号分隔付款状态S表示付款成功状态F代表失败
$ext_param1 = intval($_REQUEST['ext_param1']); /// 商家数据包,原样返回
$sign = $_REQUEST['sign']; /// MD5签名结果
$transfer = Sql::select('a.id, a.app_key, a.dev_key, a.market_key, b.heepay_transferkey, b.heepay_3deskey, b.signkey, a.account_list, a.notice_url, a.return_url, a.status, a.notify_status, a.version')
->from('syweb_core_transferinfo a, syweb_market b')
->where('a.market_key = b.market_key and a.id = ?', $ext_param1)
->get($this->db, null);
if (empty($transfer))
//return new returnObject(1, 10003, '找不到对应的支付信息。');
die(NOTIFYSTATUS_FAIL);
else
$transfer = $transfer[0];
$version = intval($transfer['version']); /// 版本号
$transfer_key = $transfer['heepay_transferkey']; /// 批付秘钥
//$detail_data_key = $transfer['heepay_3deskey']; /// 账户数据的3des加密key
$acc_list = json_decode($transfer['account_list']); /// 保存的收款账户列表
//$notice_url = $transfer['notice_url']; /// 异步回调地址
//$return_url = $transfer['return_url']; /// 同步回调地址
//$transfer_status = $transfer['status']; /// 批付状态
//$notify_status = $transfer['notify_status']; /// 通知状态
switch ($version) {
case 1:
/// 组织签名
$str = "ret_code={$ret_code}&ret_msg={$ret_msg}&agent_id={$agent_id}&hy_bill_no={$hy_bill_no}&status={$status}&batch_no={$batch_no}&batch_amt={$batch_amt}&batch_num={$batch_num}&detail_data={$detail_data}&ext_param1={$ext_param1}&key={$transfer_key}";
/// 获取sign密钥
$md5 = md5(strtolower($str));
if ($sign != $md5)
//return new returnObject(1, 10004, '签名错误。');
die(NOTIFYSTATUS_FAIL);
if ('0000' != $ret_code)
return new returnObject(1, $ret_code, $ret_msg);
/// 处理收款账户信息
if (!empty($detail_data)) {
$account_list = array();
$account = new Account_HeePay();
foreach ((array)explode('|', $detail_data) as $item) {
/// 商户流水号^收款人帐号^收款人姓名^付款金额^付款状态
$account->from_string($item);
array_push($account_list, $account);
/** @var Account_HeePay $acc */
foreach ($acc_list as $acc) {
if ($acc->mch_no == $account->mch_no) {
$acc->status = $account->status;
break;
}
}
}
}
/// 改变订单状态
if (1 == $status) /// 成功
{
Sql::update('syweb_core_transferinfo')
->setArgs(array(
'status' => TRANSFERSTATUS_SUCCESS,
'account_list' => JsonObjectToJsonString($acc_list),
'transaction_id' => $hy_bill_no,
'transfer_time' => time(),
))
->whereArgs(array('id' => $ext_param1))
->exec($this->db);
} elseif (0 == $status) { /// 未处理
Sql::update('syweb_core_transferinfo')
->setArgs(array(
'status' => TRANSFERSTATUS_NORMAL,
'account_list' => JsonObjectToJsonString($acc_list),
'transaction_id' => $hy_bill_no,
'transfer_time' => time(),
))
->whereArgs(array('id' => $ext_param1))
->exec($this->db);
} elseif (-1 == $status) { /// 无效
Sql::update('syweb_core_transferinfo')
->setArgs(array(
'status' => TRANSFERSTATUS_CANCEL,
'account_list' => JsonObjectToJsonString($acc_list),
'transaction_id' => $hy_bill_no,
'transfer_time' => time(),
))
->whereArgs(array('id' => $ext_param1))
->exec($this->db);
}
/// 发送回调通知
$this->send_order_notice();
return new returnObject(0, 0, '批量支付成功!');
default:
return new returnObject(500, -1, "不支持的接口版本:{$version}");
}
} catch(Exception $e) {
return new returnObject(1, $e->getCode(), $e->getMessage());
}
}
/**
* @note 批付前校验
* @param string $appid 应用id
* @param string $devkey 开发者key
* @param string $market_key 门店key
* @param string $account_list 收款账户列表
* @param string $notice_url 异步通知地址
* @param string $sign 签名
* @return array|returnObject
*/
private function verify_transfer_v1($appid, $devkey, $market_key, $account_list, $notice_url, $sign)
{
/// 验证公共参数是否合法
parent::init($appid, $devkey);
$verify_result = parent::verify_admin($market_key);
if (!is_error_api($verify_result))
{
/// 校验签名
$param = $_REQUEST;
if (isset($param['sign']))
{
$raw_sign = $param['sign'];
unset($param['sign']);
}
else
$raw_sign = $sign;
$own_sign = SignParameter($param, $this->marketInfo['signkey']);
if ($own_sign != $raw_sign)
return new returnObject(500, -1, '签名错误', null);
if (empty($account_list))
return new returnObject(500, 500, '请指定收款账户!', null);
//if (empty($notice_url))
// return new returnObject(500, 500, '请指定一个支付回调通知页面!', null);
/*
$referer_url = $_SERVER['HTTP_REFERER']; /// 当前调用退款的域名
$local_url = $this->getLocaleUrl(); /// 本地域名
/// 转账安全域名
$transfer_safe_domain = json_decode($this->marketInfo['transfer_safe_domain']);
$referer_paths = parse_url($referer_url);
$local_paths = parse_url($local_url);
if (!empty($referer_paths) && count($referer_paths) > 0)
{
$referer_domain = $referer_paths['host'];
$local_domain = $local_paths['host'];
if (!in_array($referer_domain, $transfer_safe_domain) && $local_domain != $referer_domain)
return new returnObject(500, 13006, '不是安全的域名。', $referer_domain);
}
*/
/// 获取附加的参数
$attach = GetAttachParameters(array('appid', 'devkey', 'market_key', 'account_list', 'notice_url', 'return_url', 'transfer_type', 'version', 'sign', ));
$attach['transfer_time'] = time();
return $attach;
}
elseif ($verify_result instanceof returnObject)
{
return $verify_result;
}
else
{
$return = new returnObject();
$return->from_array((array)$verify_result);
return $verify_result;
}
}
/**
* @note 保存转账信息
* @param string $order_no 订单号
* @param string|mixed $account_list 收款账户列表
* @param string $notice_url 异步回调地址
// * @param string $return_url 同步回调地址
* @param int $type 支付方式
* @param array $attach 支付附加参数
* @return mixed
**/
private function saved_transfer_info_v1($order_no, $account_list, $notice_url, $type, $attach)
{
$info = Sql::select('a.status')
->from('syweb_core_transferinfo a')
->where('app_key = ? and market_key = ? and order_no = ?', $this->appid, $this->market_key, $order_no)
->get($this->db, null);
if (!empty($info))
$info = $info[0];
if (!empty($info) && is_numeric($info['status']))
{
switch ($info['status'])
{
case TRANSFERSTATUS_SUCCESS:
echo '订单已成功处理!';
return false;
case TRANSFERSTATUS_CANCEL:
echo '订单已被撤销!';
return false;
case TRANSFERSTATUS_NORMAL:
break;
default:
break;
}
}
if (empty($account_list))
return null;
if (is_array($account_list) || is_object($account_list))
$account_list = JsonObjectToJsonString($account_list);
if (is_array($attach) || is_object($attach))
$attach = JsonObjectToJsonString($attach);
$record = array(
'app_key' => $this->appid, /// 应用key
'dev_key' => $this->devkey, /// 开发者key
'market_key' => $this->market_key, /// 门店key
'account_list' => $account_list, /// 收款账户列表
'notice_url' => $notice_url, /// 异步回调地址
'return_url' => '', /// 同步回调地址
'type' => $type, /// 支付方式
'order_no' => $order_no, /// 订单号
'out_trade_no' => md5(date('YmdHis') . '_' . $this->appid . '_' . $this->market_key . '_' . random(8, 1)), /// 支付唯一ID
'transaction_id' => null, /// 三方订单号
'status' => TRANSFERSTATUS_NORMAL, /// 状态
'attach' => $attach, /// 附加信息
'create_time' => time(), /// 支付创建时间戳
'notify_status' => 0, /// 回调状态
'notify_count' => 0, /// 回调次数
'version' => 1, /// 版本号
);
$this->db->beginTransaction();
try {
$id = Sql::insertInto('syweb_core_transferinfo')->values($record)->exec($this->db)->lastInsertId();
$this->db->commit();
$record['id'] = $id;
return $record;
} catch (Exception $Exception) {
$this->db->rollBack();
echo $Exception->getMessage();
return null;
}
}
/**
* @note 批量通知所有没有成功发送回调通知的记录
* @return returnObject
*/
private function send_order_notice()
{
$transfer = Sql::select('a.*, b.signkey')
->from('syweb_core_transferinfo a, syweb_market b')
->where('a.market_key = b.market_key and a.status = 1 and a.notify_status != 1 and a.notify_count < 10')
->get($this->db, null);
if (empty($transfer))
return true;
$succ_list = array();
$fail_list = array();
foreach ($transfer as $record)
{
$data = array(
'account_list' => $record['account_list'],
'order_no' => $record['order_no'],
'out_trade_no' => $record['out_trade_no'],
'transaction_id' => $record['transaction_id'],
'attach' => $record['attach'],
'version' => $record['version'],
);
$data['sign'] = SignParameter($data, $record['signkey']);
if (!empty($record['notice_url']))
$result = SendPost($record['notice_url'], $data);
else
$result = NOTIFYSTATUS_SUCCESS;
if (strcasecmp($result, NOTIFYSTATUS_SUCCESS) == 0)
array_push($succ_list, $record['id']);
else
array_push($fail_list, $record['id']);
}
$time = time();
if (!empty($succ_list) && count($succ_list) > 0)
{
if (!empty($succ_list = implode(',', $succ_list)))
$this->db->exec(/** @lang text */"update syweb_core_transferinfo set notify_status = 1, notify_count = notify_count + 1, notify_time = {$time} where id in ({$succ_list})");
}
if (!empty($fail_list) && count($fail_list) > 0)
{
if (!empty($fail_list = implode(',', $fail_list)))
$this->db->exec(/** @lang text */"update syweb_core_transferinfo set notify_status = -1, notify_count = notify_count + 1, notify_time = {$time} where id in ({$fail_list})");
}
return true;
}
/**
* @property({"default":"@db"})
* @var PDO
*/
public $db;
}