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返回Hex,decrypt期望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}¬ify_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; }