├── .gitignore ├── README.md ├── composer.json ├── doc ├── Alipay.md ├── Applepay.md └── Wechatpay.md └── src ├── Alipay ├── AppPay.php ├── BaseAlipay.php ├── BasePay.php ├── Notify.php ├── QrcodePay.php ├── Refund.php ├── Transfer.php ├── Utils │ └── Rsa2Encrypt.php ├── WapPay.php └── WebPay.php ├── Applepay └── IAP.php ├── Common ├── Curl.php └── PaymentException.php ├── Paypal ├── AppPay.php ├── BasePay.php ├── BasePaypalPay.php ├── MassPay.php ├── Notify.php ├── Refund.php └── WebPay.php ├── Unionpay ├── AppPay.php ├── BasePay.php ├── BaseUnionpay.php ├── Notify.php ├── Refund.php ├── Utils │ └── Rsa.php └── WebPay.php └── Wechatpay ├── AppPay.php ├── BasePay.php ├── BaseWechatpay.php ├── H5Pay.php ├── MpPay.php ├── NativePay.php ├── Notify.php ├── RedPack.php ├── Refund.php ├── RefundNotify.php ├── Tools.php └── Transfer.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 环境要求 2 | 3 | * PHP >= 5.6 4 | 5 | ## 当前支持的接口 6 | 7 | ### 支付宝接口 8 | 9 | * 即时到账(wap,web,app) 10 | * 交易退款接口 11 | * 单笔转账到支付宝 12 | 13 | ### 微信接口 14 | 15 | * 公众号支付 16 | * APP支付 17 | * H5支付 18 | * 扫码支付 19 | * 企业付款到零钱 20 | * 企业付款到银行卡 21 | * 交易退款接口 22 | * 公众号现金红包 23 | 24 | ### 银联 25 | 26 | * 即时到账(wap,web,app) 27 | * 交易退款接口 28 | 29 | ### Paypal 30 | 31 | * APP,网关支付 32 | * 退款 33 | * MassPay(多人转账) 34 | 35 | ### 苹果 36 | 37 | * 内购支付 38 | 39 | 40 | ## 文档 41 | 42 | * [支付宝](doc/Alipay.md) 43 | * [微信支付](doc/Wechatpay.md) 44 | * [苹果支付](doc/Applepay.md) 45 | * 银联支付 (拖延症中...等待够100star再写) 46 | * Paypal (拖延症中...等待够100star再写) 47 | 48 | ## 安装使用 49 | 50 | * 通过composer(推荐) 51 | 52 | ``` 53 | composer require "jialeo/payment" 54 | ``` 55 | 56 | * composer.json 57 | 58 | ``` 59 | "require": { 60 | "jialeo/payment": "0.*" 61 | } 62 | ``` 63 | 64 | 然后运行 65 | 66 | ``` 67 | composer update jialeo/payment 68 | ``` 69 | 70 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jialeo/payment", 3 | "description": "Alipay,Unicompay", 4 | "keywords": ["Alipay", "Unicompay"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Liang", 9 | "email": "chenjialiang@han-zi.cn" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.6.4" 14 | }, 15 | "autoload": { 16 | "psr-4": { 17 | "JiaLeo\\Payment\\": "src/" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /doc/Alipay.md: -------------------------------------------------------------------------------- 1 | # Alipay 支付宝 2 | 3 | ## 配置 4 | 5 | ```php 6 | 7 | $config = array( 8 | //支付宝分配给开发者的应用ID 9 | 'app_id' => '', 10 | 11 | //签名方式,现在只支持RSA2 12 | 'sign_type' => 'RSA2', 13 | 14 | //支付宝公钥(证书路径或key,请填写绝对路径) 15 | 'ali_public_key' => '/your/path/cert/alipay/alipay_rsa_public_key.pem', 16 | 17 | //用户应用私钥(证书路径或key,请填写绝对路径) 18 | 'rsa_private_key' => '/your/path/cert/alipay/alipay_rsa_private_key.pem', 19 | 20 | //应用公钥证书(使用公钥证书的时候需要填写,填写绝对路径) 21 | 'app_cert_path' => '/your/path/cert/alipay/alipay_app_public_key.cert', 22 | 23 | //支付宝根证书(使用公钥证书的时候需要填写,填写绝对路径) 24 | 'alipay_root_cert_path' => '/your/path/cert/alipay/alipay_root_cert.cert' 25 | 26 | //沙箱环境,正式环境改为false 27 | 'sanbox' => true 28 | 29 | ); 30 | 31 | ``` 32 | 33 | ## 即时到账接口 34 | 35 | 1. 判断设备选择实例化 36 | 37 | Web网页 38 | 39 | ```php 40 | $alipay = new \JiaLeo\Payment\Alipay\WebPay($config); 41 | ``` 42 | 43 | Wap(手机网页) 44 | 45 | ```php 46 | $alipay = new \JiaLeo\Payment\Alipay\WapPay($config); 47 | ``` 48 | 49 | App 50 | 51 | ```php 52 | $alipay = new \JiaLeo\Payment\Alipay\AppPay($config); 53 | ``` 54 | 55 | 2. 设置支付参数 56 | 57 | 代码示例: 58 | 59 | ```php 60 | $pay_data = array( 61 | 'body' => $data['body'], //内容 62 | 'subject' => $data['subject'], //标题 63 | 'out_trade_no' => $out_trade_no, //商户订单号 64 | 'timeout_express' => '30m', //取值范围:1m~15d。m-分钟,h-小时,d-天,1c-当天 65 | 'total_amount' => $data['amount'], //支付价格(单位:分) 66 | 'passback_params' => $alipay->setPassbackParams($return_params), //额外字段,回调时 67 | 'notify_url' => 'http://domain/api/alipay/notifies', //后台回调地址 68 | 'return_url' => 'http://domain/api/alipay/success' //支付成功后跳转的地址 69 | ); 70 | 71 | ``` 72 | 73 | 3. 调用执行函数 74 | 75 | 代码示例: 76 | 77 | ```php 78 | $url = $alipay->handle($pay_data); 79 | 80 | if ($data['device'] == 'app') { 81 | return $url; 82 | } else { 83 | return redirect($url); 84 | } 85 | ``` 86 | 87 | 4. 处理支付宝回调 88 | 89 | ```php 90 | $alipay = new \JiaLeo\Payment\Alipay\Notify($config); 91 | 92 | try { 93 | //验签 94 | $data = $alipay->handle(); 95 | } 96 | catch (\Exception $e) { 97 | $error_msg = $e->getMessage(); 98 | $alipay->returnFailure(); 99 | } 100 | ``` 101 | 102 | #### 退款 103 | 104 | 示例代码: 105 | 106 | ```php 107 | try{ 108 | $refund_data = [ 109 | 'out_request_no' => $out_refund_no, //退款订单号 110 | 'refund_amount' => $refund_amount, //退款金额,(单位:分) 111 | 'out_trade_no' => $out_trade_no, //订单号 112 | ]; 113 | $res = $alipay_refund->handle($refund_data); 114 | 115 | } catch (\Exception $e) { 116 | throw new ApiException($e->getMessage()); 117 | } 118 | ``` 119 | 120 | #### 转账到支付宝余额 121 | 122 | 示例代码: 123 | 124 | ```php 125 | $alipay = new \JiaLeo\Payment\Alipay\Transfer($config); 126 | 127 | $out_biz_no = date('YmdHis') . rand(10000, 99999); 128 | 129 | $payData = [ 130 | 'out_biz_no' => $out_biz_no, //订单号 131 | 'amount' => $res->amount, //转账金额,单位:分 132 | 'payee_account' => $res->alipay_account, 133 | 'remark' => '用户提现' 134 | ]; 135 | 136 | try { 137 | $body = $alipay->handle($payData); 138 | } catch (\Exception $e) { 139 | $error_msg = $e->getMessage(); 140 | throw new ApiException($error_msg); 141 | } 142 | ``` 143 | 144 | 0.17版本后使用了新的转账接口: 145 | 146 | ```php 147 | $alipay = new \JiaLeo\Payment\Alipay\Transfer($config); 148 | 149 | $out_biz_no = date('YmdHis') . rand(10000, 99999); 150 | 151 | $payData = [ 152 | 'out_biz_no' => $out_biz_no, //订单号 153 | 'order_title' => '用户提现', 154 | 'amount' => 100, //转账金额,单位:分 155 | 'payee_name' => 'ltcvdw5943', 156 | 'payee_account' => 'ltcvdw5943@sandbox.com', 157 | 'remark' => 'test' 158 | ]; 159 | 160 | try { 161 | $body = $alipay->handle($payData); 162 | } catch (\Exception $e) { 163 | $error_msg = $e->getMessage(); 164 | throw new ApiException($error_msg); 165 | } 166 | ``` 167 | -------------------------------------------------------------------------------- /doc/Applepay.md: -------------------------------------------------------------------------------- 1 | # Applypay 苹果支付 2 | 3 | 4 | ## IAP 内购支付验证 5 | 6 | 7 | 1. 实例化 8 | 9 | 10 | ``` 11 | $apple_pay = new \JiaLeo\Payment\Applepay\IAP(); 12 | ``` 13 | 14 | ``` 15 | $receipt = $_POST['receipt']; //支付凭证 16 | $sanbox = true; //是否沙盒模式 17 | 18 | //验签 19 | $data = $apple_pay->checkPay($receipt, sanbox); 20 | 21 | if ($data['status'] != 0) { 22 | throw new Exception('购买状态错误!'); 23 | } 24 | 25 | var_dump($data); 26 | ``` -------------------------------------------------------------------------------- /doc/Wechatpay.md: -------------------------------------------------------------------------------- 1 | # Wechatpay 微信支付 2 | 3 | ## 配置 4 | 5 | ```php 6 | $config = array( 7 | 'appid' => '', //填写高级调用功能的app id 8 | 'appsecret' => '', //填写高级调用功能的app secret 9 | 'mchid' => '', //商户id 10 | 'key' => '', //填写你设定的key 11 | 'sslcert_path' => '/your/path/cert/wechatpay/apiclient_cert.pem' 12 | 'sslkey_path' => '/your/path/cert/wechatpay/apiclient_key.pem', 13 | 'transfer_rsa_public_path' => '/your/path/cert/wechatpay/rsa_public.pem', //企业转账到银行卡rsa公钥证书文件路径 14 | ); 15 | ``` 16 | 17 | ## 即时到账接口 18 | 19 | 20 | 1. 判断设备选择实例化的 21 | 22 | 公众号 23 | 24 | ```php 25 | $wechatpay = new \JiaLeo\Payment\Wechatpay\MpPay($config); 26 | ``` 27 | 28 | App 29 | 30 | ```php 31 | $wechatpay = new \JiaLeo\Payment\ Wechatpay\AppPay($config); 32 | ``` 33 | 34 | H5 35 | 36 | ```php 37 | $wechatpay = new \JiaLeo\Payment\ Wechatpay\H5Pay($config); 38 | ``` 39 | 40 | Native 41 | 42 | ``` 43 | $wechatpay = new \JiaLeo\Payment\ Wechatpay\NativePay($config); 44 | ``` 45 | 46 | 47 | 48 | 示例代码: 49 | 50 | ```php 51 | if ($data['device'] == 'mp') { 52 | $wechatpay = new \JiaLeo\Payment\Wechatpay\MpPay($config); 53 | } elseif ($data['device'] == 'app') { 54 | $wechatpay = new \JiaLeo\Payment\Wechatpay\AppPay($config); 55 | } 56 | elseif($data['device'] == 'h5'){ 57 | $wechatpay = new \JiaLeo\Payment\Wechatpay\H5Pay($config); 58 | } 59 | elseif ($data['device'] == 'native') { 60 | $wechatpay = new \JiaLeo\Payment\Wechatpay\NativePay($config); 61 | } 62 | 63 | $out_trade_no = date('YmdHis') . rand(10000, 99999); 64 | 65 | $pay_data = [ 66 | 'body' => $data['body'], //内容 67 | 'attach' => $wechatpay->setPassbackParams($return_params), //商家数据包 68 | 'out_trade_no' => $out_trade_no, //商户订单号 69 | 'total_fee' => $data['amount'], //支付价格(单位:分) 70 | 'openid' => $openid, 71 | 'notify_url' => 'http://domain/api/wechatpay/notifies/' . $data['device'] //后台回调地址 72 | ]; 73 | 74 | $url = $wechatpay->handle($pay_data); 75 | ``` 76 | 77 | MpPay和AppPay返回的url为微信的签名,只要直接给到客户端给可以了。 78 | 79 | 80 | H5Pay为微信返回的url,需要额外重定向和指定支付完成后的跳转地址: 81 | 82 | NativePay返回的是一个微信支付url,使用第三方库直接转换成二维码即可 83 | 84 | ### 支付回调处理 85 | 86 | 示例代码: 87 | 88 | ```php 89 | $wechatpay = new \JiaLeo\Payment\Wechatpay\Notify($config); 90 | 91 | try { 92 | //验签 93 | $data = $wechatpay->handle(); 94 | 95 | $wechatpay->returnSuccess(); 96 | $result = true; 97 | } catch (\Exception $e) { 98 | $error_msg = $e->getMessage(); 99 | $wechatpay->returnFailure($error_msg); 100 | } 101 | ``` 102 | 103 | ### 退款 104 | 105 | 示例代码: 106 | 107 | ```php 108 | 109 | $wechatpay_refund = new \JiaLeo\Payment\Wechatpay\Refund($config); 110 | $out_refund_no = date('YmdHis').rand(10000, 99999); 111 | 112 | try { 113 | //原路退款 114 | $refund_data = [ 115 | 'out_refund_no' => $out_refund_no,//退款商户单号 116 | 'total_fee' => $amount, //原订单金额(单位分) 117 | 'refund_fee' => $refund_amount, //退款金额(单位分) 118 | 'out_trade_no' => $out_trade_no, //原订单商户单号 119 | 'refund_account' => 'REFUND_SOURCE_UNSETTLED_FUNDS' //退款资金来源-未结算资金退款 120 | 'notify_url' => 'http://domain/api/wechatpay/notifies/' . $data['device'] //后台回调地址 121 | ]; 122 | 123 | try { 124 | $wechatpay_refund->handle($refund_data); 125 | } catch(\Exception $e) { 126 | $refund_data['refund_account']='REFUND_SOURCE_RECHARGE_FUNDS'; //退款资金来源-可用余额退款 127 | $wechatpay_refund->handle($refund_data); 128 | } 129 | } 130 | ``` 131 | 132 | ### 退款回调处理 133 | 134 | 示例代码: 135 | 136 | ```php 137 | $wechatpay = new \JiaLeo\Payment\Wechatpay\RefundNotify($config); 138 | 139 | try { 140 | //验签 141 | $data = $wechatpay->handle(); 142 | 143 | $wechatpay->returnSuccess(); 144 | $result = true; 145 | } catch (\Exception $e) { 146 | $error_msg = $e->getMessage(); 147 | $wechatpay->returnFailure($error_msg); 148 | } 149 | ``` 150 | 151 | ### 企业付款 152 | 153 | * 企业付款到零钱 154 | 155 | 示例代码: 156 | 157 | ```php 158 | $config = config('payment.wechatpay.mp'); 159 | $payment = new \JiaLeo\Payment\Wechatpay\Transfer($config); 160 | $params = array( 161 | 'partner_trade_no' => time() . rand(10000, 99999), //转账订单号 162 | 'openid' => 'oErxPsxn6XTQQyFzauQW9qZYtI_k', //openid 163 | 'amount' => 100, //转账金额(单位:分) 164 | 'desc' => '测试转账', //备注 165 | //'check_name' => true //是否验证实名 166 | ); 167 | $res = $payment->handle($params); 168 | if (!$res) { 169 | var_dump($payment->errorCode, $payment->errorCodeDes); 170 | } 171 | var_dump($res); 172 | ``` 173 | 174 | * 企业付款到银行卡 175 | 176 | 1. 调用获取RSA公钥API获取RSA公钥,落地成本地文件,假设为rsa_public.pem 177 | 178 | ```php 179 | $config = config('payment.wechatpay.che'); 180 | $wechatpay = new \JiaLeo\Payment\Wechatpay\Tools($config); 181 | $url = $wechatpay->getPublicKey(); 182 | var_dump($url['pub_key']); 183 | ``` 184 | 185 | 2. PKCS#1 转 PKCS#8 (微信获取的是PKCS#1的格式,php必须读取PKCS#8) 186 | 187 | ``` 188 | openssl rsa -RSAPublicKey_in -in -pubout 189 | ``` 190 | 191 | 替换原证书内容 192 | 193 | 3. 执行 194 | 195 | ```php 196 | $config = config('payment.wechatpay.che'); 197 | $wechatpay = new \JiaLeo\Payment\Wechatpay\Transfer($config); 198 | 199 | $data = array( 200 | 'partner_trade_no' => time(), 201 | 'amount' => 100, 202 | 'bank_no' => '62148312XXXXXX', 203 | 'true_name' => 'XXX', 204 | 'bank_code' => '1001', 205 | 'desc' => 'test', 206 | ); 207 | 208 | 209 | var_dump($wechatpay->handleToBank($data)); 210 | ``` 211 | 212 | 213 | ## 红包 214 | 215 | 普通红包: 216 | 217 | ```php 218 | $config = config('payment.wechatpay.mp'); 219 | $payment = new \JiaLeo\Payment\Wechatpay\RedPack($config); 220 | 221 | $params = array( 222 | 'mch_billno' => time() . rand(10000, 99999), 223 | 'send_name' => '汉子科技', 224 | 'openid' => 'oErxPsxn6XTQQyFzauQW9qZYtI_k', 225 | 'wishing' => '汉子科技恭喜发财', 226 | 'act_name' => '汉子科技哈哈', 227 | 'remark' => '备注', 228 | 'total_amount' => 300 229 | ); 230 | $res = $payment->sendRedPack($params); 231 | 232 | if (!$res) { 233 | dump($payment->errorCode, $payment->errorCodeDes); 234 | } 235 | 236 | dump($res); 237 | ``` 238 | 239 | 裂变红包: 240 | 241 | ```php 242 | $config = config('payment.wechatpay.mp'); 243 | $payment = new \JiaLeo\Payment\Wechatpay\RedPack($config); 244 | 245 | $params = array( 246 | 'mch_billno' => time() . rand(10000, 99999), 247 | 'send_name' => '汉子科技', 248 | 'openid' => 'oErxPsxn6XTQQyFzauQW9qZYtI_k', 249 | 'wishing' => '汉子科技恭喜发财', 250 | 'act_name' => '汉子科技哈哈', 251 | 'remark' => '备注', 252 | 'total_amount' => 300, 253 | 'total_num' => 3 254 | ); 255 | $res = $payment->sendGroupRedPack($params); 256 | 257 | if (!$res) { 258 | dump($payment->errorCode, $payment->errorCodeDes); 259 | } 260 | 261 | dump($res); 262 | ``` 263 | 264 | -------------------------------------------------------------------------------- /src/Alipay/AppPay.php: -------------------------------------------------------------------------------- 1 | gateway = $this->sanboxGateway; 33 | } 34 | 35 | $this->config = $config; 36 | } 37 | 38 | /** 39 | * 检查支付宝数据 签名是否被篡改 40 | * @param array $data 41 | * @return boolean 42 | * @author helei 43 | */ 44 | public function verifySign(array $data) 45 | { 46 | $sign = $data['sign']; 47 | 48 | // 1. 剔除sign与sign_type参数 49 | unset($data['sign'], $data['sign_type']); 50 | // 2. 移除数组中的空值 51 | $data = $this->paraFilter($data); 52 | // 3. 对待签名参数数组排序 53 | $data = $this->arraySort($data); 54 | // 4. 将排序后的参数与其对应值,组合成“参数=参数值”的格式,用&字符连接起来 55 | $preStr = $this->createLinkstring($data); 56 | 57 | //提取私钥,公钥 58 | $ali_public_key = $this->getRsaKeyValue($this->config['ali_public_key'], 'public'); 59 | 60 | $rsa = new Utils\Rsa2Encrypt($ali_public_key); 61 | return $rsa->rsaVerify($preStr, $sign); 62 | } 63 | 64 | /** 65 | * 生成biz_content内容 66 | * @param array $data 67 | * @return string 68 | */ 69 | public function createbizContent(array $data) 70 | { 71 | return json_encode($data, JSON_UNESCAPED_UNICODE); 72 | } 73 | 74 | /** 75 | * 获取rsa密钥内容 76 | * @param string $key 传入的密钥信息, 可能是文件或者字符串 77 | * @param string $type 78 | * 79 | * @return string 80 | */ 81 | public function getRsaKeyValue($key, $type = 'private') 82 | { 83 | if (is_file($key)) {// 是文件 84 | $rsaKey = @file_get_contents($key); 85 | //$res = openssl_get_publickey($keyStr); 86 | } else { 87 | $keyStr = $key; 88 | 89 | $keyStr = str_replace(PHP_EOL, '', $keyStr); 90 | 91 | // 为了解决用户传入的密钥格式,这里进行统一处理 92 | if ($type === 'private') { 93 | $beginStr = ['-----BEGIN RSA PRIVATE KEY-----', '-----BEGIN PRIVATE KEY-----']; 94 | $endStr = ['-----END RSA PRIVATE KEY-----', '-----END PRIVATE KEY-----']; 95 | } else { 96 | $beginStr = ['-----BEGIN PUBLIC KEY-----', '']; 97 | $endStr = ['-----END PUBLIC KEY-----', '']; 98 | } 99 | $keyStr = str_replace($beginStr, ['', ''], $keyStr); 100 | $keyStr = str_replace($endStr, ['', ''], $keyStr); 101 | 102 | $rsaKey = $beginStr[0] . PHP_EOL . wordwrap($keyStr, 64, PHP_EOL, true) . PHP_EOL . $endStr[0]; 103 | } 104 | 105 | return $rsaKey; 106 | } 107 | 108 | /** 109 | * 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串 110 | * @param array $para 需要拼接的数组 111 | * @return string 112 | * @throws \Exception 113 | */ 114 | public function createLinkstring($para) 115 | { 116 | if (!is_array($para)) { 117 | throw new \Exception('必须传入数组参数'); 118 | } 119 | 120 | reset($para); 121 | $arg = ''; 122 | foreach ($para as $key => $val) { 123 | if (is_array($val)) { 124 | continue; 125 | } 126 | 127 | $arg .= $key . '=' . $val . '&'; 128 | } 129 | 130 | //去掉最后一个&字符 131 | $arg && $arg = substr($arg, 0, -1); 132 | 133 | //去掉转义 134 | $arg = stripslashes($arg); 135 | 136 | return $arg; 137 | } 138 | 139 | /** 140 | * 对输入的数组进行字典排序 141 | * @param array $param 需要排序的数组 142 | * @return array 143 | */ 144 | public function arraySort(array $data) 145 | { 146 | ksort($data); 147 | reset($data); 148 | 149 | return $data; 150 | } 151 | 152 | /** 153 | * 移除空值的key 154 | * @param $para 155 | * @return array 156 | */ 157 | public function paraFilter($para) 158 | { 159 | $paraFilter = []; 160 | foreach ($para as $key => $val) { 161 | if ($val === '' || $val === null) { 162 | continue; 163 | } else { 164 | if (!is_array($para[$key])) { 165 | $para[$key] = is_bool($para[$key]) ? $para[$key] : trim($para[$key]); 166 | } 167 | 168 | $paraFilter[$key] = $para[$key]; 169 | } 170 | } 171 | 172 | return $paraFilter; 173 | } 174 | 175 | /** 176 | * 设置自定义字段 177 | * @param array $data 178 | * @return string 179 | */ 180 | public function setPassbackParams(array $data) 181 | { 182 | $str = urlencode(serialize($data)); 183 | return $str; 184 | } 185 | 186 | /** 187 | * 获取自定义字段 188 | * @param $str 189 | * @return array 190 | */ 191 | public function getPassbackParams($str) 192 | { 193 | $data = unserialize(urldecode($str)); 194 | return $data; 195 | } 196 | 197 | /** 198 | * 从证书中提取序列号 199 | * @param $cert 200 | * @return string 201 | */ 202 | public function getCertSN($certPath) 203 | { 204 | $cert = file_get_contents($certPath); 205 | $ssl = openssl_x509_parse($cert); 206 | $SN = md5($this->array2string(array_reverse($ssl['issuer'])) . $ssl['serialNumber']); 207 | return $SN; 208 | } 209 | 210 | /** 211 | * 提取根证书序列号 212 | * @param $cert 根证书 213 | * @return string|null 214 | */ 215 | public function getRootCertSN($certPath) 216 | { 217 | $cert = file_get_contents($certPath); 218 | $array = explode("-----END CERTIFICATE-----", $cert); 219 | $SN = null; 220 | 221 | for ($i = 0; $i < count($array) - 1; $i++) { 222 | $ssl[$i] = openssl_x509_parse($array[$i] . "-----END CERTIFICATE-----"); 223 | if (strpos($ssl[$i]['serialNumber'], '0x') === 0) { 224 | $ssl[$i]['serialNumber'] = hexdec($ssl[$i]['serialNumber']); 225 | } 226 | if ($ssl[$i]['signatureTypeLN'] == "sha1WithRSAEncryption" || $ssl[$i]['signatureTypeLN'] == "sha256WithRSAEncryption") { 227 | if ($SN == null) { 228 | $SN = md5($this->array2string(array_reverse($ssl[$i]['issuer'])) . $ssl[$i]['serialNumber']); 229 | } else { 230 | 231 | $SN = $SN . "_" . md5($this->array2string(array_reverse($ssl[$i]['issuer'])) . $ssl[$i]['serialNumber']); 232 | } 233 | } 234 | } 235 | return $SN; 236 | } 237 | 238 | public function array2string($array) 239 | { 240 | $string = []; 241 | if ($array && is_array($array)) { 242 | foreach ($array as $key => $value) { 243 | $string[] = $key . '=' . $value; 244 | } 245 | } 246 | return implode(',', $string); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/Alipay/BasePay.php: -------------------------------------------------------------------------------- 1 | 64) { 21 | throw new PaymentException('订单号不能为空,并且长度不能超过64位'); 22 | } 23 | 24 | // 检查金额不能低于0.01 25 | if (bccomp($params['total_amount'] / 100, '0.01', 2) === -1) { 26 | throw new PaymentException('支付金额不能低于 0.01 元'); 27 | } 28 | 29 | // 检查 商品名称 与 商品描述 30 | if (empty($params['subject'])) { 31 | throw new PaymentException('必须提供 商品的标题/交易标题/订单标题/订单关键字 等'); 32 | } 33 | 34 | // 检查商品类型 35 | if (empty($params['goods_type'])) {// 默认为实物类商品 36 | $params['goods_type'] = 1; 37 | } elseif (!in_array($params['goods_type'], [0, 1])) { 38 | throw new PaymentException('商品类型可取值为:0-虚拟类商品 1-实物类商品'); 39 | } 40 | 41 | if (!$is_qrcode) { 42 | if (empty($params['product_code'])) { 43 | $params['product_code'] = $this->productCode; 44 | } 45 | } 46 | 47 | // 返回参数进行urlencode编码 48 | if (!empty($params['passback_params']) && !is_string($params['passback_params'])) { 49 | throw new PaymentException('回传参数必须是字符串'); 50 | } 51 | 52 | $params['total_amount'] = (string)($params['total_amount'] / 100); 53 | 54 | //定义公共参数 55 | $publicParams = array( 56 | 'app_id' => $this->config['app_id'], 57 | 'method' => $this->method, 58 | 'format' => 'JSON', 59 | 'charset' => 'utf-8', 60 | 'sign_type' => 'RSA2', 61 | 'timestamp' => date('Y-m-d H:i:s'), 62 | 'version' => '1.0', 63 | ); 64 | 65 | if (!empty($this->config['app_cert_path']) && !empty($this->config['alipay_root_cert_path'])) { 66 | 67 | // 68 | if (!file_exists($this->config['app_cert_path'])) { 69 | throw new PaymentException('应用证书不存在!'); 70 | } 71 | 72 | if (!file_exists($this->config['alipay_root_cert_path'])) { 73 | throw new PaymentException('支付宝根证书不存在!'); 74 | } 75 | 76 | $publicParams['app_cert_sn'] = $this->getCertSN($this->config['app_cert_path']); 77 | $publicParams['alipay_root_cert_sn'] = $this->getRootCertSN($this->config['alipay_root_cert_path']); 78 | } 79 | 80 | if (!empty($params['notify_url'])) { 81 | $publicParams['notify_url'] = $params['notify_url']; 82 | } 83 | 84 | if (!empty($params['return_url'])) { 85 | $publicParams['return_url'] = $params['return_url']; 86 | } 87 | 88 | unset($params['notify_url']); 89 | unset($params['return_url']); 90 | 91 | //生成biz_content参数 92 | $biz_content = $params; 93 | $biz_content = $this->createbizContent($biz_content); 94 | $publicParams['biz_content'] = $biz_content; 95 | 96 | //需要签名的参数 97 | $sign_params = $publicParams; 98 | 99 | //参数重新排序 100 | $sign_params = $this->arraySort($sign_params); 101 | 102 | //生成待签名字符串 103 | $str = $this->createLinkstring($sign_params); 104 | 105 | //提取私钥 106 | $rsa_private_key = $this->getRsaKeyValue($this->config['rsa_private_key']); 107 | 108 | //加密字符串 109 | $rsa = new Utils\Rsa2Encrypt($rsa_private_key); 110 | $sign = $rsa->encrypt($str); 111 | 112 | $sign_params['sign'] = $sign; 113 | 114 | if ($is_app) { 115 | return http_build_query($sign_params); 116 | } 117 | 118 | if ($is_qrcode) { 119 | //请求接口获取二维码地址 120 | $res = Curl::get($this->gateway . '?' . http_build_query($sign_params));//用get方法商品名称不会乱码 121 | 122 | return $res; 123 | } else { 124 | return $this->gateway . '?' . http_build_query($sign_params); 125 | } 126 | } 127 | } 128 | 129 | 130 | -------------------------------------------------------------------------------- /src/Alipay/Notify.php: -------------------------------------------------------------------------------- 1 | rawData = $data; 26 | 27 | //判断交易状态 28 | if (empty($data['trade_status']) || !in_array($data['trade_status'], ['TRADE_SUCCESS', 'TRADE_FINISHED'])) { 29 | throw new PaymentException('交易失败!'); 30 | } 31 | 32 | // 检查签名 33 | $flag = $this->verifySign($data); 34 | if (!$flag) { 35 | throw new PaymentException('验证签名失败!'); 36 | } 37 | 38 | return $data; 39 | } 40 | 41 | /** 42 | * 回复成功 43 | */ 44 | public function returnSuccess() 45 | { 46 | echo 'success'; 47 | } 48 | 49 | /** 50 | * 回复失败 51 | */ 52 | public function returnFailure() 53 | { 54 | echo 'failure'; 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /src/Alipay/QrcodePay.php: -------------------------------------------------------------------------------- 1 | 64) { 21 | throw new PaymentException('订单号长度不能超过64位'); 22 | } 23 | 24 | // 检查订单号是否合法 25 | if (!empty($params['trade_no']) && mb_strlen($params['trade_no']) > 64) { 26 | throw new PaymentException('支付宝交易号长度不能超过64位'); 27 | } 28 | 29 | // 检查退款请求号是否合法 30 | if (!empty($params['out_request_no']) && mb_strlen($params['out_request_no']) > 64) { 31 | throw new PaymentException('退款请求号长度不能超过64位'); 32 | } 33 | 34 | // 需要退款的金额不能低于0.01 35 | if (bccomp($params['refund_amount'] / 100, '0.01', 2) === -1) { 36 | throw new PaymentException('需要退款的金额不能低于 0.01 元'); 37 | } 38 | 39 | // 检查退款的原因说明是否合法 40 | if (!empty($params['refund_reason']) && mb_strlen($params['refund_reason']) > 256) { 41 | throw new PaymentException('退款的原因说明长度不能超过256位'); 42 | } 43 | $params['refund_amount'] = $params['refund_amount'] / 100; 44 | 45 | //定义公共参数 46 | $publicParams = array( 47 | 'app_id' => $this->config['app_id'], 48 | 'method' => 'alipay.trade.refund', 49 | 'format' => 'JSON', 50 | 'charset' => 'utf-8', 51 | 'sign_type' => 'RSA2', 52 | 'timestamp' => date('Y-m-d H:i:s'), 53 | 'version' => '1.0', 54 | ); 55 | 56 | //生成biz_content参数 57 | $biz_content = $params; 58 | $biz_content = $this->createbizContent($biz_content); 59 | $publicParams['biz_content'] = $biz_content; 60 | 61 | //需要签名的参数 62 | $sign_params = array_merge($params, $publicParams); 63 | 64 | //参数重新排序 65 | $sign_params = $this->arraySort($sign_params); 66 | 67 | //生成待签名字符串 68 | $str = $this->createLinkstring($sign_params); 69 | 70 | //提取私钥 71 | $rsa_private_key = $this->getRsaKeyValue($this->config['rsa_private_key']); 72 | 73 | //加密字符串 74 | $rsa = new Utils\Rsa2Encrypt($rsa_private_key); 75 | $sign = $rsa->encrypt($str); 76 | 77 | $sign_params['sign'] = $sign; 78 | $url = $this->gateway . '?' . http_build_query($sign_params); 79 | 80 | // 发起网络请求 81 | $curl = new \JiaLeo\Payment\Common\Curl(); 82 | $responseTxt = $curl->get($url); 83 | 84 | if (!$responseTxt ) { 85 | throw new PaymentException('网络发生错误'); 86 | } 87 | 88 | $this->refundReturnData = $responseTxt; 89 | 90 | $body = json_decode($responseTxt, true); 91 | 92 | if (!$body) { 93 | throw new PaymentException('返回数据有误!'); 94 | } 95 | 96 | if (empty($body['alipay_trade_refund_response']['code'])) { 97 | throw new PaymentException('返回数据有误!'); 98 | } 99 | 100 | if ($body['alipay_trade_refund_response']['code'] != 10000) { 101 | throw new PaymentException($body['alipay_trade_refund_response']['sub_msg']); 102 | } 103 | 104 | // 验证签名,检查支付宝返回的数据 105 | $preStr = json_encode($body['alipay_trade_refund_response'],JSON_UNESCAPED_UNICODE); 106 | 107 | $ali_public_key = $this->getRsaKeyValue($this->config['ali_public_key'], 'public'); 108 | $rsa = new Utils\Rsa2Encrypt($ali_public_key); 109 | $flag = $rsa->rsaVerify($preStr, $body['sign']); 110 | if (!$flag) { 111 | throw new PaymentException('支付宝返回数据被篡改。请检查网络是否安全!'); 112 | } 113 | 114 | return $body; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Alipay/Transfer.php: -------------------------------------------------------------------------------- 1 | 64) { 18 | throw new PaymentException('商户转账唯一订单号不能为空,长度不能超过64位'); 19 | } 20 | 21 | // 检查收款方账户是否合法 22 | if (empty($params['payee_account']) || mb_strlen($params['payee_account']) > 100) { 23 | throw new PaymentException('收款方账户不能为空,长度不能超过100位'); 24 | } 25 | 26 | if (empty($params['payee_name']) || mb_strlen($params['payee_name']) > 100) { 27 | throw new PaymentException('收款方姓名不能为空,长度不能超过100位'); 28 | } 29 | 30 | // 需要转账金额不能低于0.01 31 | if (empty($params['amount']) || bccomp($params['amount'] / 100, '0.1', 2) === -1) { 32 | throw new PaymentException('转账金额不能为空,且不能低于 0.1 元'); 33 | } 34 | 35 | $biz_params['out_biz_no'] = $params['out_biz_no']; 36 | $biz_params['trans_amount'] = (string)($params['amount'] / 100); 37 | $biz_params['product_code'] = 'TRANS_ACCOUNT_NO_PWD'; 38 | $biz_params['biz_scene'] = 'DIRECT_TRANSFER'; 39 | $biz_params['order_title'] = $params['order_title']; 40 | $biz_params['payee_info']['identity'] = $params['payee_account']; 41 | $biz_params['payee_info']['identity_type'] = 'ALIPAY_LOGON_ID'; 42 | $biz_params['payee_info']['name'] = $params['payee_name']; 43 | $biz_params['remark'] = $params['remark'] ?? ''; 44 | 45 | //定义公共参数 46 | $publicParams = array( 47 | 'app_id' => $this->config['app_id'], 48 | 'method' => 'alipay.fund.trans.uni.transfer', 49 | 'format' => 'JSON', 50 | 'charset' => 'utf-8', 51 | 'sign_type' => 'RSA2', 52 | 'timestamp' => date('Y-m-d H:i:s'), 53 | 'version' => '1.0', 54 | ); 55 | 56 | if (!empty($this->config['app_cert_path']) && !empty($this->config['alipay_root_cert_path'])) { 57 | 58 | // 59 | if (!file_exists($this->config['app_cert_path'])) { 60 | throw new PaymentException('应用证书不存在!'); 61 | } 62 | 63 | if (!file_exists($this->config['alipay_root_cert_path'])) { 64 | throw new PaymentException('支付宝根证书不存在!'); 65 | } 66 | 67 | $publicParams['app_cert_sn'] = $this->getCertSN($this->config['app_cert_path']); 68 | $publicParams['alipay_root_cert_sn'] = $this->getRootCertSN($this->config['alipay_root_cert_path']); 69 | $publicParams['alipay_root_cert_sn'] = '687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6'; 70 | } 71 | 72 | //生成biz_content参数 73 | $biz_content = $biz_params; 74 | $biz_content = $this->createbizContent($biz_content); 75 | $publicParams['biz_content'] = $biz_content; 76 | 77 | //需要签名的参数 78 | $sign_params = array_merge($params, $publicParams); 79 | 80 | //参数重新排序 81 | $sign_params = $this->arraySort($sign_params); 82 | 83 | //生成待签名字符串 84 | $str = $this->createLinkstring($sign_params); 85 | 86 | //提取私钥 87 | $rsa_private_key = $this->getRsaKeyValue($this->config['rsa_private_key']); 88 | 89 | //加密字符串 90 | $rsa = new Utils\Rsa2Encrypt($rsa_private_key); 91 | $sign = $rsa->encrypt($str); 92 | 93 | $sign_params['sign'] = $sign; 94 | $url = $this->gateway . '?' . http_build_query($sign_params); 95 | 96 | // 发起网络请求 97 | $curl = new \JiaLeo\Payment\Common\Curl(); 98 | $responseTxt = $curl->get($url); 99 | 100 | if (!$responseTxt) { 101 | throw new PaymentException('网络发生错误'); 102 | } 103 | 104 | $this->refundReturnData = $responseTxt; 105 | 106 | $body = json_decode($responseTxt, true); 107 | 108 | if (!$body) { 109 | throw new PaymentException('返回数据有误!'); 110 | } 111 | 112 | if (empty($body['alipay_fund_trans_uni_transfer_response']['code'])) { 113 | throw new PaymentException('返回数据有误!'); 114 | } 115 | 116 | // 验证签名,检查支付宝返回的数据 117 | $preStr = json_encode($body['alipay_fund_trans_uni_transfer_response'], JSON_UNESCAPED_UNICODE); 118 | 119 | $ali_public_key = $this->getRsaKeyValue($this->config['ali_public_key'], 'public'); 120 | $rsa = new Utils\Rsa2Encrypt($ali_public_key); 121 | $flag = $rsa->rsaVerify($preStr, $body['sign']); 122 | if (!$flag) { 123 | throw new PaymentException('支付宝返回数据被篡改。请检查网络是否安全!'); 124 | } 125 | 126 | return $body['alipay_fund_trans_uni_transfer_response']; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/Alipay/Utils/Rsa2Encrypt.php: -------------------------------------------------------------------------------- 1 | key = $key; 13 | } 14 | 15 | /** 16 | * 设置key 17 | * @param $key 18 | */ 19 | public function setKey($key) 20 | { 21 | $this->key = $key; 22 | } 23 | 24 | /** 25 | * RSA2签名, 此处秘钥是私有秘钥 26 | * @param string $data 签名的数组 27 | * @throws \Exception 28 | * @return string 29 | */ 30 | public function encrypt($data) 31 | { 32 | if ($this->key === false) { 33 | return ''; 34 | } 35 | 36 | $res = openssl_get_privatekey($this->key); 37 | if (empty($res)) { 38 | throw new PaymentException('您使用的私钥格式错误,请检查RSA私钥配置'); 39 | } 40 | 41 | openssl_sign($data, $sign, $res, OPENSSL_ALGO_SHA256); 42 | openssl_free_key($res); 43 | 44 | //base64编码 45 | $sign = base64_encode($sign); 46 | return $sign; 47 | } 48 | 49 | /** 50 | * RSA2解密 此处秘钥是用户私有秘钥 51 | * @param string $content 需要解密的内容,密文 52 | * @throws \Exception 53 | * @return string 54 | */ 55 | public function decrypt($content) 56 | { 57 | if ($this->key === false) { 58 | return ''; 59 | } 60 | 61 | $res = openssl_get_privatekey($this->key); 62 | if (empty($res)) { 63 | throw new PaymentException('您使用的私钥格式错误,请检查RSA私钥配置'); 64 | } 65 | 66 | //用base64将内容还原成二进制 67 | $decodes = base64_decode($content); 68 | 69 | $str = ''; 70 | $dcyCont = ''; 71 | foreach ($decodes as $n => $decode) { 72 | if (!openssl_private_decrypt($decode, $dcyCont, $res)) { 73 | echo "
" . openssl_error_string() . "
"; 74 | } 75 | $str .= $dcyCont; 76 | } 77 | 78 | openssl_free_key($res); 79 | return $str; 80 | } 81 | 82 | /** 83 | * RSA2验签 ,此处的秘钥,是第三方公钥 84 | * @param string $data 待签名数据 85 | * @param string $sign 要校对的的签名结果 86 | * @throws \Exception 87 | * @return bool 88 | */ 89 | public function rsaVerify($data, $sign) 90 | { 91 | 92 | // 初始时,使用公钥key 93 | $res = openssl_get_publickey($this->key); 94 | if (empty($res)) { 95 | throw new PaymentException('支付宝RSA公钥错误。请检查公钥文件格式是否正确'); 96 | } 97 | 98 | $result = (bool)openssl_verify($data, base64_decode($sign), $res, OPENSSL_ALGO_SHA256); 99 | openssl_free_key($res); 100 | return $result; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Alipay/WapPay.php: -------------------------------------------------------------------------------- 1 | $receipt) 30 | ); 31 | 32 | $response = $this->postData($post_data, $sanbox); 33 | 34 | return $response; 35 | } 36 | 37 | /** 38 | * 发送数据 39 | * @param $post_data 40 | * @param bool $sanbox 41 | * @return mixed 42 | * @throws PaymentException 43 | */ 44 | public function postData($post_data, $sanbox = false) 45 | { 46 | //如果是沙盒模式,请求苹果测试服务器,反之,请求苹果正式的服务器 47 | if ($sanbox) { 48 | $endpoint = 'https://sandbox.itunes.apple.com/verifyReceipt'; 49 | } else { 50 | $endpoint = 'https://buy.itunes.apple.com/verifyReceipt'; 51 | } 52 | 53 | $ch = curl_init($endpoint); 54 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 55 | curl_setopt($ch, CURLOPT_POST, true); 56 | curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); 57 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); //这两行一定要加,不加会报SSL 错误 58 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); 59 | $response = curl_exec($ch); 60 | $errno = curl_errno($ch); 61 | $errmsg = curl_error($ch); 62 | curl_close($ch); 63 | 64 | $data = json_decode($response, true); 65 | 66 | //判断时候出错,抛出异常 67 | if ($errno != 0) { 68 | throw new PaymentException('出现异常'); 69 | } 70 | 71 | //判断返回的数据是否是对象 72 | if (!is_array($data)) { 73 | throw new PaymentException('响应数据异常'); 74 | } 75 | 76 | //沙盒模式的,重新请求一下 77 | if ($data['status'] == 21007) { 78 | return $this->postData($post_data, true); 79 | } 80 | 81 | return $data; 82 | } 83 | } -------------------------------------------------------------------------------- /src/Common/Curl.php: -------------------------------------------------------------------------------- 1 | $val) { 27 | $aPOST[] = $key . "=" . urlencode($val); 28 | } 29 | $strPOST = join("&", $aPOST); 30 | } 31 | curl_setopt($oCurl, CURLOPT_URL, $url); 32 | curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1); 33 | curl_setopt($oCurl, CURLOPT_POST, true); 34 | curl_setopt($oCurl, CURLOPT_POSTFIELDS, $strPOST); 35 | curl_setopt($oCurl, CURLOPT_TIMEOUT, $tiemout); //超时时间 36 | 37 | $header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,"; 38 | $header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"; 39 | $header[] = "Cache-Control: max-age=0"; 40 | $header[] = "Connection: keep-alive"; 41 | $header[] = "Keep-Alive: 300"; 42 | $header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7"; 43 | $header[] = "Accept-Language: en-us,en;q=0.5"; 44 | $header[] = "Pragma: "; // browsers keep this blank. 45 | 46 | curl_setopt($oCurl, CURLOPT_HTTPHEADER, $header); 47 | $sContent = curl_exec($oCurl); 48 | $aStatus = curl_getinfo($oCurl); 49 | curl_close($oCurl); 50 | 51 | if (intval($aStatus["http_code"]) == 200) { 52 | return $sContent; 53 | } else { 54 | return false; 55 | } 56 | } 57 | 58 | /** 59 | * @param $url 60 | * @return bool|mixed 61 | */ 62 | public static function get($url) 63 | { 64 | $oCurl = curl_init(); 65 | if (stripos($url, "https://") !== FALSE) { 66 | curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE); 67 | curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, FALSE); 68 | curl_setopt($oCurl, CURLOPT_SSLVERSION, 1); //CURL_SSLVERSION_TLSv1 69 | } 70 | curl_setopt($oCurl, CURLOPT_URL, $url); 71 | curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1); 72 | $sContent = curl_exec($oCurl); 73 | $aStatus = curl_getinfo($oCurl); 74 | curl_close($oCurl); 75 | if (intval($aStatus["http_code"]) == 200) { 76 | return $sContent; 77 | } else { 78 | return false; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Common/PaymentException.php: -------------------------------------------------------------------------------- 1 | getMessage(); 16 | } 17 | } -------------------------------------------------------------------------------- /src/Paypal/AppPay.php: -------------------------------------------------------------------------------- 1 | checkParams($params); 15 | 16 | $params['business'] = $this->config['account'];// 告诉paypal,我的(商城的商户)Paypal账号,就是这钱是付给谁的 17 | $params['cmd'] = '_xclick'; // 告诉Paypal,我的网站是用的我自己的购物车系统 18 | $params['currency_code'] = isset($params['currency_code']) ? $params['currency_code'] : 'USD'; // //告诉Paypal,我要用什么货币。这里需要注意的是,由于汇率问题,如果网站提供了更改货币的功能,那么上面的amount也要做适当更改,paypal是不会智能的根据汇率更改总额的 19 | $params['charset'] = 'utf-8'; 20 | $params['no_note'] = '1'; 21 | $params['rm'] = '2'; 22 | $paypal_payment_url = $this->gateway . '?' . http_build_query($params); 23 | 24 | if ($is_app) { 25 | return $params; 26 | } else { 27 | return $paypal_payment_url; 28 | } 29 | } 30 | 31 | /** 32 | * 检查参数 33 | * @param $params 34 | * @throws PaymentException 35 | */ 36 | public function checkParams($params) 37 | { 38 | //检测必填参数 39 | if (!isset($params['item_name'])) { 40 | throw new PaymentException("缺少统一支付接口必填参数item_name"); 41 | } 42 | 43 | if (!isset($params['invoice']) && is_numeric($params['invoice'])) { 44 | throw new PaymentException("缺少统一支付接口必填参数invoice,并且一定为数字!"); 45 | } 46 | 47 | if (!isset($params['amount']) || mb_strlen($params['amount']) > 88) { 48 | throw new PaymentException("缺少统一支付接口必填参数amount,并且长度不能超过88位!"); 49 | } 50 | 51 | if (!isset($params['return'])) { 52 | throw new PaymentException("缺少统一支付接口必填参数return"); 53 | } 54 | 55 | if (!isset($params['cancel_return'])) { 56 | throw new PaymentException("缺少统一支付接口必填参数cancel_return"); 57 | } 58 | 59 | if (!isset($params['notify_url'])) { 60 | throw new PaymentException("缺少统一支付接口必填参数notify_url"); 61 | } 62 | 63 | if (!isset($params['custom'])) { 64 | throw new PaymentException("缺少统一支付接口必填参数custom"); 65 | } 66 | } 67 | 68 | 69 | } -------------------------------------------------------------------------------- /src/Paypal/BasePaypalPay.php: -------------------------------------------------------------------------------- 1 | config = $config; 20 | if ($config['mode'] == 'sandbox') { 21 | $this->gateway = 'https://www.sandbox.paypal.com/cgi-bin/webscr'; 22 | $this->nvp_api = 'https://api-3t.sandbox.paypal.com/nvp'; 23 | $this->end_point = 'https://api.sandbox.paypal.com'; 24 | } else { 25 | $this->gateway = 'https://www.paypal.com/cgi-bin/webscr'; 26 | $this->nvp_api = 'https://api-3t.paypal.com/nvp'; 27 | $this->end_point = 'https://api.paypal.com'; 28 | } 29 | } 30 | 31 | /** 32 | * Nvp API Http 请求 33 | * 34 | * @param string API 方法名 35 | * @param string nvp 字符串 36 | * @return array Parsed HTTP Response body 37 | */ 38 | public function nvpHttpPost($method_name, $nvp_str) 39 | { 40 | $api_username = urlencode($this->config['username']); 41 | $api_password = urlencode($this->config['password']); 42 | $api_signature = urlencode($this->config['signature']); 43 | 44 | //api 版本 45 | $version = urlencode('90'); 46 | 47 | // 设置crul参数. 48 | $ch = curl_init(); 49 | curl_setopt($ch, CURLOPT_URL, $this->nvp_api); 50 | curl_setopt($ch, CURLOPT_VERBOSE, 1); 51 | 52 | // 关闭服务器SSL验证。 53 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); 54 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); 55 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 56 | curl_setopt($ch, CURLOPT_POST, 1); 57 | 58 | // 设置API操作,版本,和请求的API签名。 59 | $nvpreq = "METHOD=$method_name&VERSION=$version&PWD=$api_password&USER=$api_username&SIGNATURE=$api_signature$nvp_str"; 60 | 61 | // 设置POST请求参数 62 | curl_setopt($ch, CURLOPT_POSTFIELDS, $nvpreq . "&" . $nvp_str); 63 | 64 | // 获取请求响应体 65 | $http_response = curl_exec($ch); 66 | 67 | if (!$http_response) { 68 | throw new PaymentException($method_name . ' 失败: ' . curl_error($ch) . '(' . curl_errno($ch) . ')', 'HTTP_REQ_ERROR'); 69 | } 70 | 71 | // 提取响应细节 72 | $http_response_ar = explode("&", $http_response); 73 | 74 | $http_parsed_response_ar = array(); 75 | foreach ($http_response_ar as $i => $value) { 76 | $tmpAr = explode("=", $value); 77 | if (sizeof($tmpAr) > 1) { 78 | $http_parsed_response_ar[$tmpAr[0]] = $tmpAr[1]; 79 | } 80 | } 81 | 82 | return $http_parsed_response_ar; 83 | } 84 | 85 | /** 86 | * 设置Nvp请求字段 87 | * @param array $data 88 | * @return string 89 | */ 90 | public function setNvpParams(array $data) 91 | { 92 | $nvp_str = ''; 93 | //遍历数组拼装nvp请求字符串 94 | foreach ($data as $i => $receiver_data) { 95 | $receiver_email = urlencode($receiver_data['receiver_email']); 96 | $amount = urlencode($receiver_data['amount']); 97 | $unique_id = urlencode($receiver_data['unique_id']); 98 | $note = urlencode($receiver_data['note']); 99 | $nvp_str .= "&L_EMAIL$i=$receiver_email&L_Amt$i=$amount&L_UNIQUEID$i=$unique_id&L_NOTE$i=$note"; 100 | } 101 | return $nvp_str; 102 | } 103 | 104 | /** 105 | * 设置自定义字段 106 | * @param array $data 107 | * @return string 108 | */ 109 | public function setPassbackParams(array $data) 110 | { 111 | $str_arr = array(); 112 | foreach ($data as $key => $v) { 113 | $str_arr[] = $key . '--' . $v; 114 | } 115 | 116 | $str = implode('---', $str_arr); 117 | return $str; 118 | } 119 | 120 | /** 121 | * 获取自定义字段 122 | * @param $str 123 | * @return array 124 | */ 125 | public function getPassbackParams($str) 126 | { 127 | $str_arr = explode('---', $str); 128 | $data = array(); 129 | foreach ($str_arr as $v) { 130 | $temp = explode('--', $v); 131 | $data[$temp[0]] = $temp[1]; 132 | } 133 | return $data; 134 | } 135 | 136 | /** 137 | * 获取发起退款的Access token 138 | * @return array [description] 139 | */ 140 | public function getAccessToken() 141 | { 142 | $client_id = $this->config['client_id']; 143 | $secret = $this->config['client_secret']; 144 | 145 | $ch = curl_init(); 146 | curl_setopt($ch, CURLOPT_URL, $this->end_point . "/v1/oauth2/token"); 147 | curl_setopt($ch, CURLOPT_HEADER, false); 148 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 149 | curl_setopt($ch, CURLOPT_POST, true); 150 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 151 | curl_setopt($ch, CURLOPT_USERPWD, $client_id . ":" . $secret); 152 | curl_setopt($ch, CURLOPT_POSTFIELDS, "grant_type=client_credentials"); 153 | 154 | $result = curl_exec($ch); 155 | curl_close($ch); 156 | if (empty($result)) return false; 157 | else { 158 | $json = json_decode($result, true); 159 | 160 | return $json['access_token']; 161 | } 162 | } 163 | 164 | public function httpPostPaypal($url, $post_data, $header = array()) 165 | { 166 | 167 | $ch = curl_init($url); 168 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); 169 | curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); 170 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 获取数据返回 171 | curl_setopt($ch, CURLOPT_HTTPHEADER, $header); // 在启用 CURLOPT_RETURNTRANSFER 时候将获取数据返回array('Expect:') 172 | $output = curl_exec($ch); 173 | 174 | if (curl_errno($ch)) { 175 | return false; 176 | } 177 | 178 | return $output; 179 | } 180 | 181 | /** 182 | * IPN验证 183 | * @param string $url 184 | * @param string $post_data 185 | * @return array 186 | */ 187 | public function notifyValidate($data) 188 | { 189 | $receiver_email = $this->config['account']; 190 | 191 | // 拼凑 post 请求数据 192 | $req = 'cmd=_notify-validate'; // 验证请求 193 | foreach ($data as $k => $v) { 194 | $v = urlencode(stripslashes($v)); 195 | $req .= "&{$k}={$v}"; 196 | } 197 | 198 | $ch = curl_init(); 199 | curl_setopt($ch, CURLOPT_URL, $this->gateway); 200 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 201 | curl_setopt($ch, CURLOPT_POST, 1); 202 | curl_setopt($ch, CURLOPT_POSTFIELDS, $req); 203 | $res = curl_exec($ch); 204 | if (!$res) { 205 | $res = curl_exec($ch); 206 | } 207 | curl_close($ch); 208 | 209 | if ($res && strcmp($res, 'VERIFIED') == 0 && $data['receiver_email'] == $receiver_email) { 210 | return $data; 211 | } else { 212 | throw new PaymentException('Paypal回调验证错误!'); 213 | } 214 | } 215 | } -------------------------------------------------------------------------------- /src/Paypal/MassPay.php: -------------------------------------------------------------------------------- 1 | checkParams($receivers, $params); 18 | 19 | //确定使用Paypal email账号为目标转账 20 | $email_subject = urlencode($params['email_subject']); 21 | $receiver_type = 'EmailAddress'; 22 | $currency = urlencode($params['currency_code']); // 其他币种 ('GBP', 'EUR', 'JPY', 'CAD', 'AUD') 23 | 24 | // 拼装请求字段 25 | $nvp_str = "&EMAILSUBJECT=$email_subject&RECEIVERTYPE=$receiver_type&CURRENCYCODE=$currency"; 26 | 27 | $receivers_array = array(); 28 | 29 | //循环重构数组 30 | for ($i = 0; $i < count($receivers); $i++) { 31 | $receivers_array[$i] = $receivers[$i]; 32 | $receivers_array[$i]['unique_id'] = 'id_' . $i; 33 | } 34 | 35 | //设置Nvp请求字段 36 | $nvp_str = $this->setNvpParams($receivers_array); 37 | 38 | // 执行Paypal Nvp API操作 39 | $result = $this->nvpHttpPost('MassPay', $nvp_str); 40 | 41 | if ("SUCCESS" == strtoupper($result["ACK"]) || "SUCCESSWITHWARNING" == strtoupper($result["ACK"])) { 42 | return true; 43 | } else { 44 | throw new PaymentException(urldecode($result['L_LONGMESSAGE0'])); 45 | } 46 | } 47 | 48 | /** 49 | * 检查参数 50 | * @param $params 51 | * @throws PaymentException 52 | */ 53 | public function checkParams($receivers, $params) 54 | { 55 | //检测必填参数 56 | if (!isset($params['email_subject'])) { 57 | throw new PaymentException("缺少MassPay接口必填参数email_subject"); 58 | } 59 | 60 | if (!isset($params['currency_code'])) { 61 | throw new PaymentException("缺少MassPay接口必填参数currency_code"); 62 | } 63 | 64 | //判断数据 65 | if (count($receivers) < 1) { 66 | throw new PaymentException("参数错误", 'PARAMS_ERROR'); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/Paypal/Notify.php: -------------------------------------------------------------------------------- 1 | rawData = $data; 26 | 27 | //IPN验证 28 | $flag = $this->notifyValidate($data); 29 | if (!$flag) { 30 | throw new PaymentException('IPN验证失败!'); 31 | } 32 | 33 | return $data; 34 | } 35 | 36 | /** 37 | * 回复成功 38 | */ 39 | public function returnSuccess() 40 | { 41 | echo 'success'; 42 | } 43 | 44 | /** 45 | * 回复失败 46 | */ 47 | public function returnFailure() 48 | { 49 | echo 'failure'; 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /src/Paypal/Refund.php: -------------------------------------------------------------------------------- 1 | end_point . "/v1/payments/sale/$txn_id/refund"; 16 | $access_token = $this->getAccessToken(); 17 | if (!$access_token) { 18 | return false; 19 | } 20 | 21 | $header = array( 22 | "Content-Type: application/json", 23 | "Authorization: Bearer $access_token" 24 | ); 25 | $output = $this->httpPostPaypal($url, '{}', $header); 26 | 27 | if (false === $output || false === $access_token) { 28 | //网络异常,请您稍后重试 29 | throw new PaymentException("网络异常,请您稍后重试"); 30 | } 31 | 32 | $output = json_decode($output, TRUE); 33 | var_dump($output); 34 | exit; 35 | if ("TRANSACTION_REFUSED" == $output['name']) { 36 | //退款已提交,请勿重复 37 | throw new PaymentException("退款已提交,请勿重复"); 38 | } 39 | 40 | if ($txn_id == $output['sale_id'] && "completed" == $output['completed']) { 41 | //退款已提交,改变订单状态等 42 | return $output; 43 | } 44 | return false; 45 | } 46 | } -------------------------------------------------------------------------------- /src/Paypal/WebPay.php: -------------------------------------------------------------------------------- 1 | transUrl = 'https://gateway.test.95516.com/gateway/api/appTransReq.do'; 24 | } 25 | else{ 26 | $this->transUrl = 'https://gateway.95516.com/gateway/api/appTransReq.do'; 27 | } 28 | } 29 | 30 | /** 31 | * 执行 32 | * @param $params 33 | * @return array 34 | * @throws PaymentException 35 | */ 36 | public function handle($params) 37 | { 38 | $test = $this->consume($params,true); 39 | 40 | $res = \JiaLeo\Payment\Common\Curl::post($test['url'],$test['params']); 41 | if(!$res){ 42 | throw new PaymentException('请求银联接口错误!'); 43 | } 44 | 45 | parse_str($res,$res_array); 46 | 47 | if( ! (is_array($res_array) && isset($res_array['respCode']))){ 48 | throw new PaymentException('请求银联接口错误!'); 49 | } 50 | 51 | return $res_array; 52 | } 53 | 54 | 55 | 56 | 57 | } -------------------------------------------------------------------------------- /src/Unionpay/BasePay.php: -------------------------------------------------------------------------------- 1 | '5.0.0', //版本号 17 | 'encoding' => 'utf-8', //编码方式 18 | 'txnType' => '01', //交易类型 19 | 'txnSubType' => '01', //交易子类 20 | 'bizType' => '000201', //业务类型 21 | 'signMethod' => '01', //签名方法 22 | 'channelType' => '08', //渠道类型,07-PC,08-手机 23 | 'accessType' => '0', //接入类型 24 | 'currencyCode' => '156', //交易币种,境内商户固定156 25 | ); 26 | 27 | /** 28 | * 消费 29 | */ 30 | public function consume(array $get_params, $is_app = false) 31 | { 32 | //验证商户订单号参数 33 | if (empty($get_params['orderId'])) { 34 | throw new PaymentException('缺少商户订单号'); 35 | } 36 | 37 | //验证缺少交易金额参数 38 | if (empty($get_params['txnAmt']) || intval($get_params['txnAmt']) < 0) { 39 | throw new PaymentException('缺少交易金额'); 40 | } 41 | 42 | //订单发送时间,格式为YYYYMMDDhhmmss,取北京时间 43 | $get_params['txnTime'] = date('YmdHis'); 44 | 45 | //商户号ID 46 | $get_params['merId'] = $this->config['mer_id']; 47 | 48 | //合并参数 49 | $params = array_merge($this->baseConsumeParams, $get_params); 50 | 51 | $cert_path = $this->config['private_key_path']; 52 | $cert_pwd = $this->config['private_key_pwd']; 53 | 54 | //获取证书key 55 | $certId = Utils\Rsa::getCertId($cert_path, $cert_pwd); 56 | $params['certId'] = $certId; 57 | 58 | //签名 59 | $params['signature'] = Utils\Rsa::getParamsSignatureWithRSA($params, $cert_path, $cert_pwd); 60 | //dd($params); 61 | 62 | if ($is_app) { 63 | return [ 64 | 'params' => $params, 65 | 'url' => $this->transUrl, 66 | ]; 67 | } else { 68 | //抛出表单---前台回调 69 | $html_form = Utils\Rsa::createAutoFormHtml($params, $this->transUrl); 70 | return $html_form; 71 | //dump( $html_form); 72 | //exit; 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /src/Unionpay/BaseUnionpay.php: -------------------------------------------------------------------------------- 1 | config = $config; 33 | } 34 | 35 | /** 36 | * 设置自定义字段 37 | * @param array $data 38 | * @return string 39 | */ 40 | public function setPassbackParams(array $data) 41 | { 42 | $str_arr = array(); 43 | foreach ($data as $key => $v) { 44 | $str_arr[] = $key . '--' . $v; 45 | } 46 | 47 | $str = implode('---', $str_arr); 48 | return $str; 49 | } 50 | 51 | /** 52 | * 获取自定义字段 53 | * @param $str 54 | * @return array 55 | */ 56 | public function getPassbackParams($str) 57 | { 58 | $str_arr = explode('---', $str); 59 | $data = array(); 60 | foreach ($str_arr as $v) { 61 | $temp = explode('--', $v); 62 | $data[$temp[0]] = $temp[1]; 63 | } 64 | return $data; 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /src/Unionpay/Notify.php: -------------------------------------------------------------------------------- 1 | rawData = $data; 25 | 26 | $res = \JiaLeo\Payment\Unionpay\Utils\Rsa::verify($data,$this->config['cert_dir']); 27 | if(!$res){ 28 | throw new PaymentException('验证失败!'); 29 | } 30 | 31 | //判断交易状态 判断respCode=00或A6即可认为交易成功 32 | if (empty($data['respCode']) || !in_array($data['respCode'], ['00', 'A6'])) { 33 | throw new PaymentException('交易失败!'); 34 | } 35 | 36 | return $data; 37 | } 38 | 39 | /** 40 | * 回复成功 41 | */ 42 | public function returnSuccess() 43 | { 44 | echo 'success'; 45 | } 46 | 47 | /** 48 | * 回复失败 49 | */ 50 | public function returnFailure() 51 | { 52 | //echo 'failure'; 53 | throw new PaymentException('failure'); 54 | } 55 | } -------------------------------------------------------------------------------- /src/Unionpay/Refund.php: -------------------------------------------------------------------------------- 1 | transUrl = 'https://gateway.test.95516.com/gateway/api/backTransReq.do'; 22 | } else { 23 | $this->transUrl = 'https://gateway.95516.com/gateway/api/backTransReq.do'; 24 | } 25 | } 26 | 27 | /** 28 | * 退款 29 | * @return mixed 30 | * @throws PaymentException 31 | */ 32 | public function handle($get_params) 33 | { 34 | //参数配置 35 | $params = [ 36 | 'version' => '5.0.0', //版本号 37 | 'encoding' => 'utf-8', //编码方式 38 | 'txnType' => '04', //交易类型 39 | 'txnSubType' => '00', //交易子类 40 | 'bizType' => '000201', //业务类型 41 | 'signMethod' => '01', //签名方法 42 | 'channelType' => '08', //渠道类型,07-PC,08-手机 43 | 'accessType' => '0', //接入类型 44 | 'currencyCode' => '156', //交易币种,境内商户固定156 45 | ]; 46 | 47 | 48 | $params['merId'] = $this->config['mer_id']; 49 | $params['txnTime'] = date('YmdHis'); 50 | 51 | $params = array_merge($params, $get_params); 52 | 53 | //签名 54 | $cert_path = $this->config['private_key_path']; 55 | $cert_pwd = $this->config['private_key_pwd']; 56 | 57 | //获取证书key 58 | $certId = Utils\Rsa::getCertId($cert_path, $cert_pwd); 59 | $params['certId'] = $certId; 60 | 61 | //签名 62 | $params['signature'] = Utils\Rsa::getParamsSignatureWithRSA($params, $cert_path, $cert_pwd);; 63 | 64 | //异步提交---后台通知地址 65 | $result = \JiaLeo\Payment\Common\Curl::post($this->transUrl, $params); 66 | 67 | //返回示例 68 | //accessType=0&bizType=000201&encoding=utf-8&merId=777290058143059 69 | //&orderId=149648615778501&origQryId=201706031828290366408&queryId=201706031835570406088 70 | //&respCode=00&respMsg=成功[0000000]&signMethod=01&txnAmt=1&txnSubType=00 71 | //&txnTime=20170603183557&txnType=04&version=5.0.0&certId=68759585097 72 | //&signature=C6m1JFv59rNmB/tiS4Q1YIAcg3Vv+U4odNUkzqlt3+D+AASmtWS4bg1jfJ3EidXxPTF+kMuk1Iy2gGzkgvfbn9BWEqEeYCV8sktusaTfOO6o2Obbz8HLRpu4zkLfWbVBA6aQ7nSrdpRl7RHO9ToFBcDdUcM07vWzGQ79cAELmrgxuvnp966/eNuQFu5jPHj2hFndduWAKM0+EIbIy/n1vcIk6kiGJGsXVcBeNoqsgjPMMMQWF2j3jTAmvQvnx7SWmwgtWE/LYSmNonehvJQv1CFa/fKkiiMIdd8XsMvJv6zbDQumzJz3lQ67mWMRq+YDBEyH+onzwSjO4IWXeJz9ig== 73 | 74 | if (!$result) { 75 | throw new PaymentException('请求银联接口错误!'); 76 | } 77 | 78 | $res_array=$this->strToArray($result); 79 | 80 | if (!(is_array($res_array) && isset($res_array['respCode']))) { 81 | throw new PaymentException('请求银联接口错误!'); 82 | } 83 | 84 | if ($res_array['respCode'] != '00') { 85 | throw new PaymentException('退款失败!原因:' . $res_array['respMsg']); 86 | } 87 | 88 | //验签 89 | $res = \JiaLeo\Payment\Unionpay\Utils\Rsa::verify($res_array,$this->config['cert_dir']); 90 | if(!$res){ 91 | throw new PaymentException('签名验证失败!'); 92 | } 93 | 94 | return $res_array; 95 | } 96 | 97 | 98 | public function strToArray($data) 99 | { 100 | $temp = array(); 101 | $arr = explode('&',$data); 102 | 103 | foreach($arr as $key => $v){ 104 | $str = explode('=',$v); 105 | $temp[$str[0]] = $str[1]; 106 | } 107 | 108 | return $temp; 109 | 110 | } 111 | } -------------------------------------------------------------------------------- /src/Unionpay/Utils/Rsa.php: -------------------------------------------------------------------------------- 1 | 153 | 154 | 155 | 156 | 157 |
158 | 159 | eot; 160 | foreach ($params as $key => $value) { 161 | $html .= "\n"; 162 | } 163 | $html .= << 165 | 166 | 167 | eot; 168 | 169 | return $html; 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /src/Unionpay/WebPay.php: -------------------------------------------------------------------------------- 1 | transUrl = 'https://gateway.test.95516.com/gateway/api/frontTransReq.do'; 25 | } 26 | else{ 27 | $this->transUrl = 'https://gateway.95516.com/gateway/api/frontTransReq.do'; 28 | } 29 | } 30 | 31 | /** 32 | * 执行 33 | * @param $params 34 | * @return array 35 | * @throws PaymentException 36 | */ 37 | public function handle($params) 38 | { 39 | return $this->consume($params); 40 | } 41 | 42 | 43 | } -------------------------------------------------------------------------------- /src/Wechatpay/AppPay.php: -------------------------------------------------------------------------------- 1 | pay($params); 28 | $time = time(); 29 | 30 | $pay_sigin_data = array( 31 | 'appid' => $this->config['appid'], 32 | 'timestamp' => "$time", 33 | 'noncestr' => $this->getNonceStr(), 34 | 'package' => 'Sign=WXPay', 35 | 'partnerid' => $this->config['mchid'], 36 | 'prepayid' => $pay_info['prepay_id'] 37 | ); 38 | 39 | //签名 40 | $pay_sigin_data['sign'] = $this->makeSign($pay_sigin_data); 41 | 42 | return $pay_sigin_data; 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /src/Wechatpay/BasePay.php: -------------------------------------------------------------------------------- 1 | checkParams($params); 19 | 20 | 21 | $nonce_str = $this->getNonceStr(); 22 | 23 | if (!isset($params['create_ip'])) { 24 | $ip_address = !empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']; 25 | //兼容处理 26 | $ip_address_arr = explode(',', $ip_address); 27 | $ip_address = $ip_address_arr[0]; 28 | 29 | } else { 30 | $ip_address = $params['create_ip']; 31 | } 32 | 33 | //必传参数 34 | $data = array( 35 | 'appid' => $this->config['appid'], 36 | 'mch_id' => $this->config['mchid'], 37 | 'device_info' => $this->device, 38 | 'trade_type' => $this->tradeType, 39 | 'nonce_str' => $nonce_str, 40 | 41 | 'spbill_create_ip' => $ip_address, 42 | 'out_trade_no' => $params['out_trade_no'], 43 | 'body' => $params['body'], 44 | 'total_fee' => $params['total_fee'], 45 | 'attach' => $params['attach'], 46 | 'notify_url' => $params['notify_url'] 47 | ); 48 | 49 | //用户标识 50 | if (isset($params['openid'])) { 51 | $data['openid'] = $params['openid']; 52 | } 53 | 54 | //订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。 55 | if (isset($params['time_start'])) { 56 | $data['time_start'] = $params['time_start']; 57 | } 58 | 59 | //订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。 60 | //注意:最短失效时间间隔必须大于5分钟 61 | if (isset($params['time_expire'])) { 62 | $data['time_expire'] = $params['time_expire']; 63 | } 64 | 65 | //dump($data); 66 | 67 | //进行签名 68 | $data['sign'] = $this->makeSign($data); 69 | 70 | //转换成xml 71 | $xml = $this->toXml($data); 72 | $result = $this->postXmlCurl($this->gateway . $this->payUrl, $xml); 73 | $get_result = $this->fromXml($result); 74 | 75 | 76 | if (!isset($get_result['return_code']) || $get_result['return_code'] != 'SUCCESS') { 77 | throw new PaymentException('调起支付失败!错误信息:' . isset($get_result['return_msg']) ? $get_result['return_msg'] : $get_result); 78 | } 79 | 80 | //验证签名 81 | $check_sigin = $this->makeSign($get_result); 82 | if ($check_sigin != $get_result['sign']) { 83 | throw new PaymentException('调起支付失败!错误信息:返回数据验证数据失败!'); 84 | } 85 | 86 | //验证交易是否成功 87 | if (empty($get_result['result_code']) || $get_result['result_code'] !== 'SUCCESS') { 88 | throw new PaymentException('生成微信单号失败,' . $get_result['return_msg']); 89 | }; 90 | 91 | //验证返回参数 92 | if (!array_key_exists("appid", $get_result) || $get_result['appid'] != $this->config['appid'] || !array_key_exists("prepay_id", $get_result) || $get_result['prepay_id'] == "") { 93 | throw new PaymentException("返回数据参数错误"); 94 | } 95 | 96 | return $get_result; 97 | } 98 | 99 | /** 100 | * 检查参数 101 | * @param $params 102 | * @throws PaymentException 103 | */ 104 | public function checkParams($params) 105 | { 106 | //检测必填参数 107 | if (!isset($params['out_trade_no']) || mb_strlen($params['out_trade_no']) > 32) { 108 | throw new PaymentException("缺少统一支付接口必填参数out_trade_no,并且长度不能超过32位!"); 109 | } 110 | 111 | if (!isset($params['body']) || mb_strlen($params['body']) > 128) { 112 | throw new PaymentException("缺少统一支付接口必填参数body,并且长度不能超过128位!"); 113 | } 114 | 115 | if (!isset($params['total_fee']) || mb_strlen($params['total_fee']) > 88) { 116 | throw new PaymentException("缺少统一支付接口必填参数total_fee,并且长度不能超过88位!"); 117 | } 118 | 119 | if (!in_array($this->tradeType, ['JSAPI', 'NATIVE', 'APP', 'MWEB'])) { 120 | throw new PaymentException("trade_type参数错误!"); 121 | } 122 | 123 | //关联参数 124 | if ($this->tradeType == "JSAPI" && !isset($params['openid'])) { 125 | throw new PaymentException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!"); 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /src/Wechatpay/BaseWechatpay.php: -------------------------------------------------------------------------------- 1 | config = $config; 29 | } 30 | 31 | /** 32 | * 生成签名 33 | * @return string 签名 34 | */ 35 | public function makeSign($params) 36 | { 37 | //签名步骤一:按字典序排序参数 38 | ksort($params); 39 | $string = $this->toUrlParams($params); 40 | //签名步骤二:在string后加入KEY 41 | $string = $string . "&key=" . $this->config['key']; 42 | //签名步骤三:MD5加密 43 | $string = md5($string); 44 | //签名步骤四:所有字符转为大写 45 | $result = strtoupper($string); 46 | return $result; 47 | } 48 | 49 | /** 50 | * 格式化参数格式化成url参数 51 | */ 52 | public function toUrlParams($params) 53 | { 54 | $buff = ""; 55 | foreach ($params as $k => $v) { 56 | if ($k != "sign" && $v != "" && !is_array($v)) { 57 | $buff .= $k . "=" . $v . "&"; 58 | } 59 | } 60 | 61 | $buff = trim($buff, "&"); 62 | return $buff; 63 | } 64 | 65 | /** 66 | * 产生随机字符串,不长于32位 67 | * @param int $length 68 | * @return string 产生的随机字符串 69 | */ 70 | public static function getNonceStr($length = 32) 71 | { 72 | $chars = "abcdefghijklmnopqrstuvwxyz0123456789"; 73 | $str = ""; 74 | for ($i = 0; $i < $length; $i++) { 75 | $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1); 76 | } 77 | return $str; 78 | } 79 | 80 | /** 81 | * 输出xml字符 82 | * @throws PaymentException 83 | **/ 84 | public function toXml($params) 85 | { 86 | if (!is_array($params) 87 | || count($params) <= 0 88 | ) { 89 | throw new PaymentException("数组数据异常!"); 90 | } 91 | 92 | $xml = ""; 93 | foreach ($params as $key => $val) { 94 | if (is_numeric($val)) { 95 | $xml .= "<" . $key . ">" . $val . ""; 96 | } else { 97 | $xml .= "<" . $key . ">"; 98 | } 99 | } 100 | $xml .= ""; 101 | return $xml; 102 | } 103 | 104 | /** 105 | * 将xml转为array 106 | * @param string $xml 107 | * @throws PaymentException 108 | */ 109 | public function fromXml($xml) 110 | { 111 | if (!$xml) { 112 | throw new PaymentException("xml数据异常!"); 113 | } 114 | //将XML转为array 115 | //禁止引用外部xml实体 116 | $bPreviousValue = libxml_disable_entity_loader(true); 117 | $params = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); 118 | libxml_disable_entity_loader($bPreviousValue); 119 | 120 | return $params; 121 | } 122 | 123 | /** 124 | * 以post方式提交xml到对应的接口url 125 | * 126 | * @param string $xml 需要post的xml数据 127 | * @param string $url url 128 | * @param string $sslcert_path cert路径,默认不需要 129 | * @param string $sslkey_path 是否需要证书,默认不需要 130 | * @param int $second url执行超时时间,默认30s 131 | * @throws PaymentException 132 | */ 133 | protected function postXmlCurl($url, $xml, $sslcert_path = '', $sslkey_path = '', $second = 30) 134 | { 135 | $ch = curl_init(); 136 | //设置超时 137 | curl_setopt($ch, CURLOPT_TIMEOUT, $second); 138 | 139 | //如果有配置代理这里就设置代理 140 | /*curl_setopt($ch,CURLOPT_PROXY, "0.0.0.0"); 141 | curl_setopt($ch,CURLOPT_PROXYPORT, 8080);*/ 142 | 143 | curl_setopt($ch, CURLOPT_URL, $url); 144 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, TRUE); 145 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);//严格校验 146 | //设置header 147 | curl_setopt($ch, CURLOPT_HEADER, FALSE); 148 | //要求结果为字符串且输出到屏幕上 149 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); 150 | 151 | if (!empty($sslcert_path) && !empty($sslkey_path)) { 152 | //设置证书 153 | //使用证书:cert 与 key 分别属于两个.pem文件 154 | curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM'); 155 | curl_setopt($ch, CURLOPT_SSLCERT, $sslcert_path); 156 | curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM'); 157 | curl_setopt($ch, CURLOPT_SSLKEY, $sslkey_path); 158 | } 159 | 160 | //post提交方式 161 | curl_setopt($ch, CURLOPT_POST, TRUE); 162 | curl_setopt($ch, CURLOPT_POSTFIELDS, $xml); 163 | 164 | //运行curl 165 | $data = curl_exec($ch); 166 | 167 | //返回结果 168 | if ($data) { 169 | curl_close($ch); 170 | return $data; 171 | } else { 172 | $error = curl_errno($ch); 173 | curl_close($ch); 174 | throw new PaymentException("curl出错,错误码:$error"); 175 | } 176 | } 177 | 178 | /** 179 | * 设置自定义字段 180 | * @param array $data 181 | * @return string 182 | */ 183 | public function setPassbackParams(array $data) 184 | { 185 | $str_arr = array(); 186 | foreach ($data as $key => $v) { 187 | $str_arr[] = $key . '--' . $v; 188 | } 189 | 190 | $str = implode('---', $str_arr); 191 | return $str; 192 | } 193 | 194 | /** 195 | * 获取自定义字段 196 | * @param $str 197 | * @return array 198 | */ 199 | public function getPassbackParams($str) 200 | { 201 | $str_arr = explode('---', $str); 202 | $data = array(); 203 | foreach ($str_arr as $v) { 204 | $temp = explode('--', $v); 205 | $data[$temp[0]] = $temp[1]; 206 | } 207 | return $data; 208 | } 209 | 210 | } -------------------------------------------------------------------------------- /src/Wechatpay/H5Pay.php: -------------------------------------------------------------------------------- 1 | pay($params); 28 | return $pay_info['mweb_url']; 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/Wechatpay/MpPay.php: -------------------------------------------------------------------------------- 1 | pay($params); 27 | $time = time(); 28 | 29 | $pay_sigin_data = array( 30 | 'appId' => $this->config['appid'], 31 | 'timeStamp' => "$time", 32 | 'nonceStr' => $this->getNonceStr(), 33 | 'signType' => 'MD5', 34 | 'package' => 'prepay_id=' . $pay_info['prepay_id'], 35 | ); 36 | 37 | //签名 38 | $pay_sigin_data['paySign'] = $this->makeSign($pay_sigin_data); 39 | 40 | return $pay_sigin_data; 41 | } 42 | 43 | 44 | } -------------------------------------------------------------------------------- /src/Wechatpay/NativePay.php: -------------------------------------------------------------------------------- 1 | pay($params); 28 | return $pay_info['code_url']; 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/Wechatpay/Notify.php: -------------------------------------------------------------------------------- 1 | fromXml($postdata); 22 | 23 | $this->rawData = $get_notify; 24 | 25 | try { 26 | 27 | if (!isset($get_notify['return_code']) || $get_notify['return_code'] != 'SUCCESS') { 28 | throw new PaymentException('支付返回失败'); 29 | } 30 | 31 | if (!isset($get_notify['sign'])) { 32 | throw new PaymentException('缺少sign字段'); 33 | } 34 | 35 | //验证签名 36 | if ($this->makeSign($get_notify) != $get_notify['sign']) { 37 | throw new PaymentException('验证签名失败!'); 38 | } 39 | 40 | } catch (PaymentException $e) { 41 | $this->errorMsg = $e->getMessage(); 42 | return false; 43 | } 44 | 45 | return $get_notify; 46 | } 47 | 48 | /** 49 | * 回复成功 50 | */ 51 | public function returnSuccess() 52 | { 53 | $return = array( 54 | 'return_code' => 'SUCCESS', 55 | 'return_msg' => 'OK' 56 | ); 57 | echo $this->toXml($return); 58 | } 59 | 60 | /** 61 | * 回复失败 62 | */ 63 | public function returnFailure($error_msg) 64 | { 65 | $return = array( 66 | 'return_code' => 'FAIL', 67 | 'return_msg' => $error_msg 68 | ); 69 | echo $this->toXml($return); 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /src/Wechatpay/RedPack.php: -------------------------------------------------------------------------------- 1 | payUrl = '/mmpaymkttransfers/sendgroupredpack'; 38 | $params['amt_type'] = 'ALL_RAND'; 39 | 40 | return $this->handle($params); 41 | } 42 | 43 | /** 44 | * 发送普通红包 45 | * @param $params 46 | * @return mixed 47 | * @throws PaymentException 48 | */ 49 | public function sendRedPack($params) 50 | { 51 | $this->payUrl = '/mmpaymkttransfers/sendredpack'; 52 | 53 | return $this->handle($params); 54 | } 55 | 56 | /** 57 | * 发放红包 58 | * @return mixed 59 | * @throws PaymentException 60 | */ 61 | public function handle($params) 62 | { 63 | //检查订单号是否合法 64 | if (empty($params['mch_billno'])) { 65 | throw new PaymentException('mch_billno商户订单号不能为空'); 66 | } 67 | 68 | //检查订单号是否合法 69 | if (empty($params['send_name'])) { 70 | throw new PaymentException('send_name商户名称不能为空'); 71 | } 72 | 73 | //检查openid 74 | if (empty($params['openid'])) { 75 | throw new PaymentException('openid不能为空'); 76 | } 77 | 78 | //检查祝福语 79 | if (empty($params['wishing'])) { 80 | throw new PaymentException('wishing祝福语不能为空'); 81 | } 82 | 83 | //检查活动名称 84 | if (empty($params['act_name'])) { 85 | throw new PaymentException('act_name活动名称不能为空'); 86 | } 87 | 88 | //检查备注 89 | if (empty($params['remark'])) { 90 | throw new PaymentException('remark备注不能为空'); 91 | } 92 | 93 | // 需要红包金额不能低于1 94 | if (empty($params['total_amount']) || $params['total_amount'] < 100) { 95 | throw new PaymentException('total_amount红包金额不能为空,且不能低于 1 元'); 96 | } 97 | 98 | //红包金额大于200,需要传scene_id 99 | if ($params['total_amount'] > 20000 && (empty($params['scene_id']) || !in_array($params['scene_id'], self::SCENEID))) { 100 | throw new PaymentException('红包金额大于200元,scene_id不能为空或scene_id值错误!'); 101 | } 102 | 103 | if (!isset($params['create_ip'])) { 104 | $ip_address = !empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']; 105 | //兼容处理 106 | $ip_address_arr = explode(',', $ip_address); 107 | $ip_address = $ip_address_arr[0]; 108 | 109 | } else { 110 | $ip_address = $params['create_ip']; 111 | } 112 | 113 | $data = array( 114 | 'wxappid' => $this->config['appid'], 115 | 'mch_id' => $this->config['mchid'], 116 | 'nonce_str' => $this->getNonceStr(), 117 | 'mch_billno' => $params['mch_billno'], 118 | 'send_name' => $params['send_name'], 119 | 're_openid' => $params['openid'], 120 | 'total_amount' => $params['total_amount'], 121 | 'total_num' => empty($params['total_num']) ? 1 : $params['total_num'], 122 | 'wishing' => $params['wishing'], 123 | 'act_name' => $params['act_name'], 124 | 'remark' => $params['remark'], 125 | 'client_ip' => $ip_address, 126 | ); 127 | 128 | //场景id 129 | if (!empty($params['scene_id'])) { 130 | $data['scene_id'] = $params['scene_id']; 131 | } 132 | 133 | //活动信息 134 | if (!empty($params['risk_info'])) { 135 | $data['risk_info'] = $params['risk_info']; 136 | } 137 | 138 | //资金授权商户号 139 | if (!empty($params['consume_mch_id'])) { 140 | $data['consume_mch_id'] = $params['consume_mch_id']; 141 | } 142 | 143 | //发放类型 144 | if (!empty($params['amt_type'])) { 145 | $data['amt_type'] = $params['amt_type']; 146 | } 147 | 148 | //签名 149 | $data['sign'] = $this->makeSign($data); 150 | 151 | //转换成xml 152 | $xml = $this->toXml($data); 153 | $result = $this->postXmlCurl($this->gateway . $this->payUrl, $xml, $this->config['sslcert_path'], $this->config['sslkey_path']); 154 | $get_result = $this->fromXml($result); 155 | 156 | try { 157 | if (!isset($get_result['return_code']) || $get_result['return_code'] != 'SUCCESS') { 158 | throw new PaymentException('调起支付失败!错误信息:' . isset($get_result['return_msg']) ? $get_result['return_msg'] : $get_result); 159 | } 160 | 161 | if ($get_result['result_code'] != 'SUCCESS') { 162 | throw new PaymentException('调起支付失败!错误信息:' . isset($get_result['err_code_des']) ? $get_result['err_code_des'] : $get_result); 163 | } 164 | } catch (\Exception $e) { 165 | $this->errorCode = $get_result['err_code']; 166 | $this->errorCodeDes = $get_result['err_code_des']; 167 | 168 | return false; 169 | } 170 | 171 | return $get_result; 172 | } 173 | } -------------------------------------------------------------------------------- /src/Wechatpay/Refund.php: -------------------------------------------------------------------------------- 1 | checkParams($params); 26 | $data = array( 27 | 'appid' => $this->config['appid'], 28 | 'mch_id' => $this->config['mchid'], 29 | 'nonce_str' => $this->getNonceStr(), 30 | 'out_refund_no' => $params['out_refund_no'], 31 | 'total_fee' => $params['total_fee'], 32 | 'refund_fee' => $params['refund_fee'], 33 | ); 34 | 35 | if (isset($params['out_trade_no'])) { 36 | $data['out_trade_no'] = $params['out_trade_no']; 37 | } else { 38 | $data['transaction_id'] = $params['transaction_id']; 39 | } 40 | 41 | if (isset($params['refund_account'])) { 42 | $data['refund_account'] = $params['refund_account']; 43 | } 44 | 45 | if (isset($params['notify_url'])) { 46 | $data['notify_url'] = $params['notify_url']; 47 | } 48 | 49 | if (isset($params['refund_desc'])) { 50 | $data['refund_desc'] = $params['refund_desc']; 51 | } 52 | 53 | //签名 54 | $data['sign'] = $this->makeSign($data); 55 | $this->rawData = $data; 56 | $xml = $this->toXml($data); 57 | $url = $this->gateway . $this->payUrl; 58 | 59 | $res = $this->postXmlCurl($url, $xml, $this->config['sslcert_path'], $this->config['sslkey_path']); 60 | $get_result = $this->fromXml($res); 61 | 62 | if (!$get_result) { 63 | throw new PaymentException('退款接口数据返回解析失败!'); 64 | } 65 | 66 | $this->refundReturnData = $get_result; 67 | $this->refundReturnCode = $get_result['return_code']; 68 | $this->refundResultCode = isset($get_result['result_code']) ? $get_result['result_code'] : ''; 69 | 70 | if (!isset($get_result['return_code']) || $get_result['return_code'] != 'SUCCESS') { 71 | throw new PaymentException('退款失败!接口返回错误信息:' . (isset($get_result['return_msg']) ? $get_result['return_msg'] : '')); 72 | } 73 | 74 | if (!isset($get_result['result_code']) || $get_result['result_code'] != 'SUCCESS') { 75 | throw new PaymentException('退款失败!接口返回错误信息:' . (isset($get_result['err_code_des']) ? $get_result['err_code_des'] : '')); 76 | } 77 | 78 | //验证签名 79 | $result_sign = $this->makeSign($get_result); 80 | if ($result_sign != $get_result['sign']) { 81 | throw new PaymentException('返回数据签名失败!'); 82 | } 83 | 84 | return $get_result; 85 | } 86 | 87 | /** 88 | * 验证参数 89 | * @param $params 90 | * @return bool 91 | * @throws PaymentException 92 | */ 93 | public function checkParams($params) 94 | { 95 | //验证 96 | if (empty($params['out_trade_no']) && empty($params['transaction_id'])) { 97 | throw new PaymentException('out_trade_no字段或transaction_id字段中的一个不能为空!'); 98 | } 99 | 100 | if (isset($params['out_trade_no']) && mb_strlen($params['out_trade_no']) > 32) { 101 | throw new PaymentException('out_trade_no字段不能大于32位!'); 102 | } 103 | 104 | if (isset($params['transaction_id']) && mb_strlen($params['transaction_id']) > 28) { 105 | throw new PaymentException('微信订单号,transaction_id字段不能大于28位!'); 106 | } 107 | 108 | if (empty($params['out_refund_no']) || mb_strlen($params['out_refund_no']) > 64) { 109 | throw new PaymentException('商户退款单号(out_refund_no)不能为空,且不能大于64位!'); 110 | } 111 | 112 | if (empty($params['total_fee']) || mb_strlen($params['total_fee']) > 100) { 113 | throw new PaymentException('订单金额(total_fee)不能为空,且不能大于100位!'); 114 | } 115 | 116 | if (empty($params['refund_fee']) || mb_strlen($params['refund_fee']) > 100) { 117 | throw new PaymentException('退款金额(refund_fee)不能为空,且不能大于100位!'); 118 | } 119 | 120 | if (isset($params['refund_desc']) && mb_strlen($params['refund_desc']) > 80) { 121 | throw new PaymentException('退款原因(refund_desc)能大于80位!'); 122 | } 123 | 124 | if (isset($params['refund_account']) && !in_array($params['refund_account'], ['REFUND_SOURCE_RECHARGE_FUNDS', 'REFUND_SOURCE_UNSETTLED_FUNDS'])) { 125 | throw new PaymentException('退款资金来源(refund_account)的值只能为REFUND_SOURCE_RECHARGE_FUNDS或REFUND_SOURCE_UNSETTLED_FUNDS!'); 126 | } 127 | 128 | return true; 129 | } 130 | 131 | 132 | } -------------------------------------------------------------------------------- /src/Wechatpay/RefundNotify.php: -------------------------------------------------------------------------------- 1 | fromXml($postdata); 28 | if (!$get_notify) { 29 | return false; 30 | } 31 | 32 | $this->rawData = $get_notify; 33 | 34 | try { 35 | 36 | if (!isset($get_notify['return_code']) || $get_notify['return_code'] != 'SUCCESS') { 37 | throw new PaymentException('退款返回失败'); 38 | } 39 | 40 | if (!isset($get_notify['req_info'])) { 41 | throw new PaymentException('缺少req_info字段'); 42 | } 43 | 44 | //解密数据 45 | $req_info = base64_decode($get_notify['req_info']); 46 | $md5_key = md5($this->config['key']); 47 | $decrypted = openssl_decrypt($req_info, 'AES-256-ECB', $md5_key, OPENSSL_RAW_DATA); 48 | if (!$decrypted) { 49 | throw new PaymentException('解密数据失败!'); 50 | } 51 | 52 | $decrypted_arr = $this->fromXml($decrypted); 53 | 54 | $get_notify = array_merge($get_notify, $decrypted_arr); 55 | 56 | } catch (PaymentException $e) { 57 | $this->errorMsg = $e->getMessage(); 58 | return false; 59 | } 60 | 61 | return $get_notify; 62 | } 63 | 64 | /** 65 | * 回复成功 66 | */ 67 | public function returnSuccess() 68 | { 69 | $return = array( 70 | 'return_code' => 'SUCCESS', 71 | 'return_msg' => 'OK' 72 | ); 73 | echo $this->toXml($return); 74 | } 75 | 76 | /** 77 | * 回复失败 78 | */ 79 | public function returnFailure($error_msg) 80 | { 81 | $return = array( 82 | 'return_code' => 'FAIL', 83 | 'return_msg' => $error_msg 84 | ); 85 | echo $this->toXml($return); 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /src/Wechatpay/Tools.php: -------------------------------------------------------------------------------- 1 | $this->config['mchid'], 24 | 'nonce_str' => $this->getNonceStr(), 25 | 'sign_type' => 'MD5' 26 | ); 27 | 28 | //签名 29 | $data['sign'] = $this->makeSign($data); 30 | 31 | //转换成xml 32 | $xml = $this->toXml($data); 33 | $result = $this->postXmlCurl($url, $xml, $this->config['sslcert_path'], $this->config['sslkey_path']); 34 | $get_result = $this->fromXml($result); 35 | 36 | try { 37 | if (!isset($get_result['return_code']) || $get_result['return_code'] != 'SUCCESS') { 38 | throw new PaymentException('调起获取RSA公钥接口失败!错误信息:' . isset($get_result['return_msg']) ? $get_result['return_msg'] : $get_result); 39 | } 40 | 41 | if ($get_result['result_code'] != 'SUCCESS') { 42 | throw new PaymentException('调起获取RSA公钥接口失败!错误信息:' . isset($get_result['err_code_des']) ? $get_result['err_code_des'] : $get_result); 43 | } 44 | 45 | if (empty($get_result['pub_key'])) { 46 | throw new PaymentException('获取pub_key字段错误!'); 47 | } 48 | } catch (\Exception $e) { 49 | $this->errorCode = $get_result['err_code']; 50 | $this->errorCodeDes = $get_result['err_code_des']; 51 | 52 | return false; 53 | } 54 | 55 | return $get_result; 56 | } 57 | 58 | 59 | } -------------------------------------------------------------------------------- /src/Wechatpay/Transfer.php: -------------------------------------------------------------------------------- 1 | '工商银行', 19 | '1005' => '农业银行', 20 | '1026' => '中国银行', 21 | '1003' => '建设银行', 22 | '1001' => '招商银行', 23 | '1066' => '邮储银行', 24 | '1020' => '交通银行', 25 | '1004' => '浦发银行', 26 | '1006' => '民生银行', 27 | '1009' => '兴业银行', 28 | '1010' => '平安银行', 29 | '1021' => '中信银行', 30 | '1025' => '华夏银行', 31 | '1027' => '广发银行', 32 | '1022' => '光大银行', 33 | '1032' => '北京银行', 34 | '1056' => '宁波银行', 35 | ); 36 | 37 | /** 38 | * 企业付款到零钱 39 | * @return mixed 40 | * @throws PaymentException 41 | */ 42 | public function handle($params) 43 | { 44 | //检查订单号是否合法 45 | if (empty($params['partner_trade_no'])) { 46 | throw new PaymentException('商户订单号不能为空'); 47 | } 48 | 49 | // 需要转账金额不能低于1 50 | if (empty($params['amount']) || $params['amount'] < 100) { 51 | throw new PaymentException('转账金额不能为空,且不能低于 1 元'); 52 | } 53 | 54 | if (empty($params['openid'])) { 55 | throw new PaymentException('openid不能为空'); 56 | } 57 | 58 | $is_check_name = !empty($params['check_name']) ? true : false; 59 | if (empty($params['re_user_name']) && $is_check_name) { 60 | throw new PaymentException('校验用户姓名选项为验证时,收款用户姓名(re_user_name)不能为空'); 61 | } 62 | 63 | if (!isset($params['create_ip'])) { 64 | $ip_address = !empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']; 65 | //兼容处理 66 | $ip_address_arr = explode(',', $ip_address); 67 | $ip_address = $ip_address_arr[0]; 68 | 69 | } else { 70 | $ip_address = $params['create_ip']; 71 | } 72 | 73 | $data = array( 74 | 'mch_appid' => $this->config['appid'], 75 | 'mchid' => $this->config['mchid'], 76 | 'nonce_str' => $this->getNonceStr(), 77 | 'partner_trade_no' => $params['partner_trade_no'], 78 | 'amount' => $params['amount'], 79 | 'openid' => $params['openid'], 80 | 'check_name' => !$is_check_name ? 'NO_CHECK' : 'FORCE_CHECK', 81 | 'spbill_create_ip' => $ip_address, 82 | ); 83 | 84 | if ($is_check_name) { 85 | $data['re_user_name'] = $params['re_user_name']; 86 | } 87 | 88 | if (!empty($params['desc'])) { 89 | $data['desc'] = $params['desc']; 90 | } 91 | 92 | //签名 93 | $data['sign'] = $this->makeSign($data); 94 | 95 | //转换成xml 96 | $xml = $this->toXml($data); 97 | $result = $this->postXmlCurl($this->gateway . $this->payUrl, $xml, $this->config['sslcert_path'], $this->config['sslkey_path']); 98 | $get_result = $this->fromXml($result); 99 | 100 | try { 101 | if (!isset($get_result['return_code']) || $get_result['return_code'] != 'SUCCESS') { 102 | throw new PaymentException('调起转账失败!错误信息:' . isset($get_result['return_msg']) ? $get_result['return_msg'] : $get_result); 103 | } 104 | 105 | if ($get_result['result_code'] != 'SUCCESS') { 106 | throw new PaymentException('调起转账失败!错误信息:' . isset($get_result['err_code_des']) ? $get_result['err_code_des'] : $get_result); 107 | } 108 | } catch (\Exception $e) { 109 | $this->errorCode = $get_result['err_code']; 110 | $this->errorCodeDes = $get_result['err_code_des']; 111 | 112 | return false; 113 | } 114 | 115 | 116 | return $get_result; 117 | } 118 | 119 | /** 120 | * 转账到银行卡 121 | * @param $params 122 | * @return bool 123 | * @throws PaymentException 124 | */ 125 | public function handleToBank($params) 126 | { 127 | $this->payUrl = '/mmpaysptrans/pay_bank'; 128 | 129 | //检查订单号是否合法 130 | if (empty($params['partner_trade_no'])) { 131 | throw new PaymentException('商户订单号不能为空'); 132 | } 133 | 134 | // 需要转账金额不能低于1 135 | if (empty($params['amount']) || $params['amount'] < 100) { 136 | throw new PaymentException('转账金额不能为空,且不能低于 1 元'); 137 | } 138 | 139 | if (empty($params['bank_no'])) { 140 | throw new PaymentException('bank_no不能为空'); 141 | } 142 | 143 | if (empty($params['true_name'])) { 144 | throw new PaymentException('true_name不能为空'); 145 | } 146 | 147 | if (empty($params['bank_code']) || !isset($this->bankCode[$params['bank_code']])) { 148 | throw new PaymentException('bank_code不能为空或银行编号错误!'); 149 | } 150 | 151 | $data = array( 152 | 'mch_id' => $this->config['mchid'], 153 | 'nonce_str' => $this->getNonceStr(), 154 | 'partner_trade_no' => $params['partner_trade_no'], 155 | 'amount' => $params['amount'], 156 | 'enc_bank_no' => $this->rsaPublicEncrypt($params['bank_no']), 157 | 'enc_true_name' => $this->rsaPublicEncrypt($params['true_name']), 158 | 'bank_code' => $params['bank_code'], 159 | 160 | ); 161 | 162 | !isset($params['desc']) ?: $data['desc'] = $params['desc']; 163 | 164 | //签名 165 | $data['sign'] = $this->makeSign($data); 166 | 167 | //转换成xml 168 | $xml = $this->toXml($data); 169 | $result = $this->postXmlCurl($this->gateway . $this->payUrl, $xml, $this->config['sslcert_path'], $this->config['sslkey_path']); 170 | $get_result = $this->fromXml($result); 171 | 172 | try { 173 | if (!isset($get_result['return_code']) || $get_result['return_code'] != 'SUCCESS') { 174 | throw new PaymentException('调起转账失败!错误信息:' . isset($get_result['return_msg']) ? $get_result['return_msg'] : $get_result); 175 | } 176 | 177 | if ($get_result['result_code'] != 'SUCCESS') { 178 | throw new PaymentException('调起转账失败!错误信息:' . isset($get_result['err_code_des']) ? $get_result['err_code_des'] : $get_result); 179 | } 180 | } catch (\Exception $e) { 181 | $this->errorCode = $get_result['err_code']; 182 | $this->errorCodeDes = $get_result['err_code_des']; 183 | 184 | return false; 185 | } 186 | 187 | return $get_result; 188 | } 189 | 190 | /** 191 | * 查询企业打款信息 192 | * @param $params 193 | * $params[partner_trade_no] int 商户订单号 194 | * $params[nonce_str] string 随机字符串,长度小于32位 195 | * @return bool|mixed 196 | * @throws PaymentException 197 | */ 198 | public function queryBankOrder($params) 199 | { 200 | $this->payUrl = '/mmpaysptrans/query_bank'; 201 | 202 | $param = [ 203 | 'mch_id' => $this->config['mchid'], 204 | 'partner_trade_no' => $params['partner_trade_no'], 205 | 'nonce_str' => $params['nonce_str'], 206 | ]; 207 | 208 | // 签名 209 | $sign = $this->makeSign($param); 210 | $param['sign'] = $sign; 211 | 212 | //转换成xml 213 | $xml = $this->toXml($param); 214 | $result = $this->postXmlCurl($this->gateway . $this->payUrl, $xml, $this->config['sslcert_path'], $this->config['sslkey_path']); 215 | $get_result = $this->fromXml($result); 216 | 217 | try { 218 | if (!isset($get_result['return_code']) || $get_result['return_code'] != 'SUCCESS') { 219 | throw new PaymentException('查询企业打款到银行卡信息!错误信息:' . isset($get_result['return_msg']) ? $get_result['return_msg'] : $get_result); 220 | } 221 | 222 | if ($get_result['result_code'] != 'SUCCESS') { 223 | throw new PaymentException('查询企业打款到银行卡信息!错误信息:' . isset($get_result['err_code_des']) ? $get_result['err_code_des'] : $get_result); 224 | } 225 | } catch (\Exception $e) { 226 | $this->errorCode = $get_result['err_code']; 227 | $this->errorCodeDes = $get_result['err_code_des']; 228 | 229 | return false; 230 | } 231 | 232 | return $get_result; 233 | } 234 | 235 | /** 236 | * RSA公钥加密 237 | * @param $str 238 | * @return string 239 | * @throws PaymentException 240 | */ 241 | public function rsaPublicEncrypt($str) 242 | { 243 | $publicstr = file_get_contents($this->config['transfer_rsa_public_path']); 244 | $publickey = openssl_pkey_get_public($publicstr); // 读取公钥 245 | if (!$publickey) { 246 | throw new PaymentException('读取公钥错误!'); 247 | } 248 | 249 | $r = openssl_public_encrypt($str, $encrypted, $publickey, OPENSSL_PKCS1_OAEP_PADDING); 250 | 251 | if (!$r) { 252 | throw new PaymentException('公钥加密失败错误!'); 253 | } 254 | return base64_encode($encrypted); 255 | } 256 | } --------------------------------------------------------------------------------