├── .gitignore ├── demo ├── log.txt ├── images │ ├── alipay.gif │ └── new-btn-fixed.png ├── composer.json ├── config.php ├── return_url.php ├── notify_url.php ├── alipayapi.php ├── readme.txt └── index.html ├── cacert.pem ├── alipay_public_key.pem ├── composer.json ├── README.md └── Alipay.php /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | composer.lock 3 | vendor 4 | -------------------------------------------------------------------------------- /demo/log.txt: -------------------------------------------------------------------------------- 1 | 集成时请注意: 2 | 本文件log.txt请不要删除。 3 | 该文本用于写日志函数。 4 | -------------------------------------------------------------------------------- /cacert.pem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gytai/alipay-php-sdk/master/cacert.pem -------------------------------------------------------------------------------- /demo/images/alipay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gytai/alipay-php-sdk/master/demo/images/alipay.gif -------------------------------------------------------------------------------- /demo/images/new-btn-fixed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gytai/alipay-php-sdk/master/demo/images/new-btn-fixed.png -------------------------------------------------------------------------------- /alipay_public_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnxj/9qwVfgoUh/y2W89L6BkRA 3 | FljhNhgPdyPuBV64bfQNN1PjbCzkIM6qRdKBoLPXmKKMiFYnkd6rAoprih3/PrQE 4 | B/VsW8OoM8fxn67UDYuyBTqA23MML9q1+ilIZwBC2AQ2UBVOrFXfFl75p6/B5Ksi 5 | NG9zpgmLCUYuLkxpLQIDAQAB 6 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /demo/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mytharcher/alipay-sdk-test", 3 | "license": "MIT", 4 | "authors": [ 5 | { 6 | "name": "mytharcher", 7 | "email": "mytharcher@gmail.com" 8 | } 9 | ], 10 | "require": { 11 | "php": ">=5.3.0", 12 | "mytharcher/alipay-php-sdk": "dev-master", 13 | "mobiledetect/mobiledetectlib": "^2.8" 14 | } 15 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mytharcher/alipay-php-sdk", 3 | "version": "0.2.0", 4 | "description": "Refactored Alipay PHP SDK (include mobile version). 经重构的支付宝 PHP 开发包(含移动网页支付)", 5 | "keywords": ["Alipay", "PHP", "SDK", "支付宝"], 6 | "license": "MIT", 7 | "support": { 8 | "issues": "https://github.com/mytharcher/alipay-php-sdk/issues", 9 | "source": "https://github.com/mytharcher/alipay-php-sdk" 10 | }, 11 | "authors": [ 12 | { 13 | "name": "mytharcher", 14 | "email": "mytharcher@gmail.com" 15 | } 16 | ], 17 | "require": { 18 | "php": ">=5.3.0" 19 | }, 20 | "autoload": { 21 | "psr-4": {"mytharcher\\sdk\\alipay\\": "./"} 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /demo/config.php: -------------------------------------------------------------------------------- 1 | 56 | -------------------------------------------------------------------------------- /demo/return_url.php: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | 24 | 25 | 26 | 支付宝即时到账交易接口 27 | 28 | 29 | isMobile(); 33 | 34 | $alipay = new mytharcher\sdk\alipay\Alipay($alipay_config, $is_mobile); 35 | $verify_result = $alipay->verifyCallback(); 36 | if($verify_result) {//验证成功 37 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 38 | //请在这里加上商户的业务逻辑程序代码 39 | 40 | //——请根据您的业务逻辑来编写程序(以下代码仅作参考)—— 41 | //获取支付宝的通知返回参数,可参考技术文档中页面跳转同步通知参数列表 42 | 43 | //商户订单号 44 | 45 | $out_trade_no = $_GET['out_trade_no']; 46 | 47 | //支付宝交易号 48 | 49 | $trade_no = $_GET['trade_no']; 50 | 51 | //交易状态 52 | $trade_status = $_GET['trade_status']; 53 | 54 | 55 | if($_GET['trade_status'] == 'TRADE_FINISHED' || $_GET['trade_status'] == 'TRADE_SUCCESS') { 56 | //判断该笔订单是否在商户网站中已经做过处理 57 | //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序 58 | //如果有做过处理,不执行商户的业务程序 59 | } 60 | else { 61 | echo "trade_status=".$_GET['trade_status']; 62 | } 63 | 64 | echo "验证成功
"; 65 | 66 | //——请根据您的业务逻辑来编写程序(以上代码仅作参考)—— 67 | 68 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 69 | } 70 | else { 71 | //验证失败 72 | //如要调试,请看alipay_notify.php页面的verifyReturn函数 73 | echo "验证失败"; 74 | } 75 | ?> 76 | 77 | 78 | -------------------------------------------------------------------------------- /demo/notify_url.php: -------------------------------------------------------------------------------- 1 | verifyCallback(TRUE); 26 | 27 | if($verify_result) {//验证成功 28 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 29 | //请在这里加上商户的业务逻辑程序代 30 | 31 | 32 | //——请根据您的业务逻辑来编写程序(以下代码仅作参考)—— 33 | 34 | //获取支付宝的通知返回参数,可参考技术文档中服务器异步通知参数列表 35 | 36 | //商户订单号 37 | 38 | $out_trade_no = $_POST['out_trade_no']; 39 | 40 | //支付宝交易号 41 | 42 | $trade_no = $_POST['trade_no']; 43 | 44 | //交易状态 45 | $trade_status = $_POST['trade_status']; 46 | 47 | 48 | if($_POST['trade_status'] == 'TRADE_FINISHED') { 49 | //判断该笔订单是否在商户网站中已经做过处理 50 | //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序 51 | //如果有做过处理,不执行商户的业务程序 52 | 53 | //注意: 54 | //该种交易状态只在两种情况下出现 55 | //1、开通了普通即时到账,买家付款成功后。 56 | //2、开通了高级即时到账,从该笔交易成功时间算起,过了签约时的可退款时限(如:三个月以内可退款、一年以内可退款等)后。 57 | 58 | //调试用,写文本函数记录程序运行情况是否正常 59 | //logResult("这里写入想要调试的代码变量值,或其他运行的结果记录"); 60 | } 61 | else if ($_POST['trade_status'] == 'TRADE_SUCCESS') { 62 | //判断该笔订单是否在商户网站中已经做过处理 63 | //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序 64 | //如果有做过处理,不执行商户的业务程序 65 | 66 | //注意: 67 | //该种交易状态只在一种情况下出现——开通了高级即时到账,买家付款成功后。 68 | 69 | //调试用,写文本函数记录程序运行情况是否正常 70 | //logResult("这里写入想要调试的代码变量值,或其他运行的结果记录"); 71 | } 72 | 73 | //——请根据您的业务逻辑来编写程序(以上代码仅作参考)—— 74 | 75 | echo "success"; //请不要修改或删除 76 | 77 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 78 | } 79 | else { 80 | //验证失败 81 | echo "fail"; 82 | 83 | //调试用,写文本函数记录程序运行情况是否正常 84 | //logResult("这里写入想要调试的代码变量值,或其他运行的结果记录"); 85 | } 86 | ?> 87 | -------------------------------------------------------------------------------- /demo/alipayapi.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 支付宝即时到账交易接口接口 7 | 8 | isMobile(); 66 | //建立请求 67 | $alipay = new mytharcher\sdk\alipay\Alipay($alipay_config, $is_mobile); 68 | 69 | if ($is_mobile) { 70 | $params = $alipay->prepareMobileTradeData(array( 71 | 'out_trade_no' => $out_trade_no, 72 | 'subject' => $subject, 73 | 'body' => $body, 74 | 'total_fee' => $total_fee, 75 | 'merchant_url' => 'http://'.$_SERVER['HTTP_HOST'], 76 | 'req_id' => date('Ymdhis-') 77 | )); 78 | echo $alipay->buildRequestFormHTML($params, 'get'); 79 | } else { 80 | echo $alipay->buildRequestFormHTML(array( 81 | "service" => "create_direct_pay_by_user", 82 | "partner" => trim($alipay_config['partner']), 83 | "payment_type" => $alipay_config['payment_type'], 84 | "notify_url" => $alipay_config['notify_url'], 85 | "return_url" => $alipay_config['return_url'], 86 | "seller_id" => $alipay_config['partner'], 87 | "out_trade_no" => $out_trade_no, 88 | "subject" => $subject, 89 | "total_fee" => $total_fee, 90 | "body" => $body, 91 | "show_url" => $show_url, 92 | "anti_phishing_key" => $anti_phishing_key, 93 | "exter_invoke_ip" => $exter_invoke_ip, 94 | "_input_charset" => trim(strtolower($alipay_config['input_charset'])) 95 | ), "post"); 96 | } 97 | 98 | ?> 99 | 100 | 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 支付宝即时到账 SDK 简化版(含移动网页支付) 3 | ========== 4 | 5 | 该项目精简和重构了官方的 SDK 开发包,将签名参数和验证返回合并在一个类里,仅一个文件,引入方便,调用简单。 6 | 7 | 8 | 适用范围 9 | ---------- 10 | 11 | * 即时到账支付(含移动网页版) 12 | * 各类 PHP 项目(含各种框架) 13 | * 有一定 PHP 项目经验(能 debug 类库源码)的开发者 14 | 15 | 16 | 17 | 使用方式 18 | ---------- 19 | 20 | ### Composer 包 ### 21 | 22 | $ composer require mytharcher/alipay-php-sdk 23 | 24 | 或者在已有`composer.json`中添加: 25 | 26 | "require": { 27 | "mytharcher/alipay-php-sdk": "dev-master" 28 | } 29 | 30 | ### 普通引入 ### 31 | 32 | 1. 下载项目代码,将`Alipay.php`和`cacert.pem`放置到项目的合适位置; 33 | 2. 如果使用移动端签名需要同时下载alipay_public_key.pem到合适位置 34 | 3. 使用框架加载第三方类库方法,或者直接引入`Alipay.php`; 35 | 36 | ### 支付前 ### 37 | 38 | 支付前需要将订单相关数据按支付宝的接口规则准备好,并在客户端生成表单由用户发起支付提交。 39 | 40 | 桌面版和移动网页版接口不同,需要由两种方式生成数据。 41 | 42 | 注意,桌面版和移动版需要自行根据`userAgent`进行判断(推荐:[Mobile Detect](https://github.com/serbanghita/Mobile-Detect))。 43 | 44 | #### 桌面版 #### 45 | 46 | 引入类库后,创建一个实例,调用`buildRequestForm`方法来生成支付表单: 47 | 48 | $alipay = new Alipay(array(/* config */)); 49 | 50 | $body = $alipay->buildRequestFormHTML(array( 51 | 'out_trade_no' => $order['id'], 52 | 'subject' => '杜蕾斯xx', 53 | 'total_fee' => $order['price'], 54 | 'body' => $order['category']['name'], 55 | 'show_url' => 'http://'.$_SERVER['HTTP_HOST'].'/product/xx', 56 | 'anti_phishing_key' => '', 57 | 'exter_invoke_ip' => '', 58 | 'it_b_pay' => $this->setting['paymentTimeout'] / 60 . 'm', 59 | '_input_charset' => $this->config->item('input_charset', 'alipay') 60 | )); 61 | 62 | // 输出 HTML 到浏览器,JS 会自动发起提交 63 | echo $body; 64 | 65 | #### 移动网页 #### 66 | 67 | 移动网页版初始化实例时需要传入第二个参数`wap`表明是移动版网页支付: 68 | 69 | $alipay = new Alipay(array(/*...*/), 'wap'); 70 | 71 | 在生成提交表单之前需要一个额外的步骤,调用`prepareMobileTradeData`方法会在后端发起一次预支付提交到支付宝服务器,并准备好要生成的参数,提交参数也略有不同: 72 | 73 | $params = $alipay->prepareMobileTradeData(array( 74 | 'out_trade_no' => $order['id'] 75 | 'subject' => '杜蕾斯xx', 76 | 'body' => $order['category']['name'], 77 | 'total_fee' => $order['price'], 78 | 'merchant_url' => 'http://'.$_SERVER['HTTP_HOST'].'/product/xx', 79 | 'req_id' => date('Ymdhis-').$order['id'] 80 | )) 81 | 82 | // 移动网页版接口只支持 GET 方式提交 83 | $body = $alipay->buildRequestFormHTML($params, 'get'); 84 | 85 | 有时候需要适配微信内的支付跳转,由于双方竞争关系导致无法自动跳转(参见:《[关于微信公众平台无法使用支付宝收付款的解决方案说明](https://cshall.alipay.com/enterprise/help_detail.htm?help_id=524702)》),则可以不生成表单,直接使用`$params`参数数组在页面生成支付链接,提示用户用浏览器打开支付。 86 | 87 | ### 支付后 ### 88 | 89 | 支付后不再区分桌面版和移动网页版,都可以调用统一的接口验证返回信息。唯一区别是分为**同步**和**异步**两种方式,而且这两种方式会根据收到的请求数据自动判断完成,使用者无需关心。 90 | 91 | $alipay = new Alipay(array(/* config... */)); 92 | // 获得验证结果 true/false 93 | $result = $alipay->verifyCallback(); 94 | 95 | 调用接口后根据结果取值进行后续的业务处理,如订单成功支付完成,或者支付失败等。 96 | 97 | 注意:正常情况下,支付宝的异步通知模式会比返回更早调用,在业务处理中需要考虑重复调用的情况。 98 | 99 | ### APP支付 服务端生成签名以及验签 100 | 101 | 移动APP签名支持 RSA 102 | 103 | > 注意:rsa签名验签时的公钥是支付提供的,不是自己生成rsa签名时生成的公钥 104 | 105 | 106 | ``` 107 | //以下配置必须 108 | $config['sign_type'] = 'RSA'; 109 | $config['private_key_path'] = '';//rsa私钥路径 110 | 111 | 112 | $alipay = new Alipay(/* config... */,'app'); 113 | $params = array( 114 | 'out_trade_no' => 324242342342, 115 | 'subject' => '主题 产品名称',, 116 | 'total_fee' => '0.01',, 117 | '_input_charset' => 'utf-8', 118 | 'sign_type' => 'RSA' 119 | ); 120 | $paramStr = $alipay->buildSignedParametersForApp(/*$params*/); //此代码可以直接给APP端提交 121 | 122 | ``` 123 | 验签: 124 | 125 | ``` 126 | $config['sign_type'] = 'RSA'; 127 | $alipay = new Alipay(/* config... */,'app'); 128 | // 获得验证结果 true/false 129 | $result = $alipay->verifyCallback(); 130 | ``` 131 | 132 | 133 | API 134 | ---------- 135 | 136 | 主要的几个方法如下: 137 | 138 | ### `new Alipay($config = array(), $type='')` ### 139 | 140 | - $config 配置数组 141 | - $type wap 移动网页支付 /app 移动APP /其他PC支付 142 | 143 | 构造方法,创建支付对象实例。 144 | 145 | 参数`$config`:支付配置数组,可用参数列表: 146 | 147 | array( 148 | // 即时到账方式 149 | 'payment_type' => 1, 150 | // 传输协议 151 | 'transport' => 'http', 152 | // 编码方式 153 | 'input_charset' => 'utf-8', 154 | // 签名方法 155 | 'sign_type' => 'MD5', 156 | // 支付完成异步通知调用地址 157 | 'notify_url' => 'http://'.$_SERVER['HTTP_HOST'.'>/order/callback_alipay/notify', 158 | // 支付完成同步返回地址 159 | 'return_url' => 'http://'.$_SERVER['HTTP_HOST'.'>/order/callback_alipay/return', 160 | // 证书路径 161 | 'cacert' => APPPATH.'third_party/alipay/cacert.pem', 162 | // 支付宝商家 ID 163 | 'partner' => '2088xxxxxxxx', 164 | // 支付宝商家 KEY 165 | 'key' => 'xxxxxxxxxxxx', 166 | // 支付宝商家注册邮箱 167 | 'seller_email' => 'email@domain.com' 168 | ) 169 | 170 | 参数`$is_mobile`:当前是否是移动支付,默认`FALSE`(需要使用者自行判断,见前文)。 171 | 172 | ### `buildSignedParameters(array(/*params*/))` ### 173 | 174 | 根据交易信息创建准备提交的已签名参数组。主要用于自定义生成提交信息页面,当不需要按标准方式输出自动提交表单时,可以通过该方法准备好要提交的参数,自行处理提交页面。 175 | 176 | $params = $alipay->buildSignedParameters(array(/*params*/)); 177 | 178 | 参数见“支付前”部分。 179 | 180 | ### `buildRequestFormHTML(array(/*params*/))` ### 181 | 182 | 根据交易信息创建自动提交表单的 HTML 内容。参数同上。 183 | 184 | // 向客户端页面输出所有要提交的参数表单内容 185 | echo $alipay->buildRequestFormHTML($params); 186 | 187 | ### `prepareMobileTradeData(array(/*params*/))` ### 188 | 189 | 移动网页版准备提交数据。由于移动网页支付分为两个步骤,第一步在后端向支付宝服务器发起一次预提交,获取 token,之后才由客户端带着 token 一起提交准备完成的参数。 190 | 191 | $params = $alipay->prepareMobileTradeData(array(/*params*/)); 192 | // 将准备好的参数生成表单输出到客户端自动提交 193 | echo $alipay->buildRequestFormHTML($params); 194 | 195 | ### `verifyCallback()` ### 196 | 197 | 对支付完成返回结果的验证。合并了异步通知模式和同步返回模式的参数验证,均可通过调用此接口完成。且是否是移动模式也会自动判断。 198 | 199 | $alipay = new Alipay(array(/* config */)); 200 | // 获得验证结果 true/false 201 | $result = $alipay->verifyCallback(); 202 | 203 | 大多数情况使用这几个接口就可以完成支付,其他子功能详见源码中相应函数注释。 204 | 205 | 206 | 吐槽 207 | ---------- 208 | 209 | 懒得吐槽原来的接口代码了。 210 | 211 | 212 | MIT Licensed 213 | ---------- 214 | 215 | -EOF- 216 | -------------------------------------------------------------------------------- /demo/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | ╭───────────────────────╮ 3 | ────┤ 支付宝代码示例结构说明 ├──── 4 | ╰───────────────────────╯ 5 |   6 |   接口名称:支付宝即时到账交易接口(create_direct_pay_by_user) 7 |     代码版本:3.3 8 | 开发语言:PHP 9 | 版 权:支付宝(中国)网络技术有限公司 10 |   制 作 者:支付宝商户事业部技术支持组 11 | 联系方式:商户服务电话0571-88158090 12 | 13 | ───────────────────────────────── 14 | 15 | ─────── 16 | 代码文件结构 17 | ─────── 18 | 19 | create_direct_pay_by_user-php-UTF-8 20 | │ 21 | ├lib┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈类文件夹 22 | │ │ 23 | │ ├alipay_core.function.php ┈┈┈┈┈┈支付宝接口公用函数文件 24 | │ │ 25 | │ ├alipay_notify.class.php┈┈┈┈┈┈┈支付宝通知处理类文件 26 | │ │ 27 | │ ├alipay_submit.class.php┈┈┈┈┈┈┈支付宝各接口请求提交类文件 28 | │ │ 29 | │ └alipay_md5.function.php┈┈┈┈┈┈┈支付宝接口MD5函数文件 30 | │ 31 | ├log.txt┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈日志文件 32 | │ 33 | ├alipay.config.php┈┈┈┈┈┈┈┈┈┈┈┈基础配置类文件 34 | │ 35 | ├alipayapi.php┈┈┈┈┈┈┈┈┈┈┈┈┈┈支付宝接口入口文件 36 | │ 37 | ├notify_url.php ┈┈┈┈┈┈┈┈┈┈┈┈┈服务器异步通知页面文件 38 | │ 39 | ├return_url.php ┈┈┈┈┈┈┈┈┈┈┈┈┈页面跳转同步通知文件 40 | │ 41 | ├cacert.pem ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈用于CURL中校验SSL的CA证书文件 42 | │ 43 | └readme.txt ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈使用说明文本 44 | 45 | ※注意※ 46 | 47 | 1、必须开启curl服务 48 | (1)使用Crul需要修改服务器中php.ini文件的设置,找到php_curl.dll去掉前面的";"即可 49 | (2)文件夹中cacert.pem文件请务必放置到商户网站平台中(如:服务器上),并且保证其路径有效,提供的代码demo中的默认路径是当前文件夹下——getcwd().'\\cacert.pem' 50 | 51 | 2、需要配置的文件是: 52 | alipay.config.php 53 | alipayapi.php 54 | 55 | ●本代码示例(DEMO)采用fsockopen()的方法远程HTTP获取数据、采用DOMDocument()的方法解析XML数据。 56 | 57 | 请根据商户网站自身情况来决定是否使用代码示例中的方式—— 58 | 如果不使用fsockopen,那么建议用curl来代替; 59 | 如果环境不是PHP5版本或其以上,那么请用其他方法代替DOMDocument()。 60 | 61 | curl、XML解析方法需您自行编写代码。 62 | 63 | 64 | ───────── 65 | 类文件函数结构 66 | ───────── 67 | 68 | alipay_core.function.php 69 | 70 | function createLinkstring($para) 71 | 功能:把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串 72 | 输入:Array $para 需要拼接的数组 73 | 输出:String 拼接完成以后的字符串 74 | 75 | function createLinkstringUrlencode($para) 76 | 功能:把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串,并对参数值urlencode 77 | 输入:Array $para 需要拼接的数组 78 | 输出:String 拼接完成以后的字符串 79 | 80 | function paraFilter($para) 81 | 功能:除去数组中的空值和签名参数 82 | 输入:Array $para 签名参数组 83 | 输出:Array 去掉空值与签名参数后的新签名参数组 84 | 85 | function argSort($para) 86 | 功能:对数组排序 87 | 输入:Array $para 排序前的数组 88 | 输出:Array 排序后的数组 89 | 90 | function logResult($word='') 91 | 功能:写日志,方便测试(看网站需求,也可以改成存入数据库) 92 | 输入:String $word 要写入日志里的文本内容 默认值:空值 93 | 94 | function getHttpResponsePOST($url, $cacert_url, $para, $input_charset = '') 95 | 功能:远程获取数据,POST模式 96 | 输入:String $url 指定URL完整路径地址 97 | String $cacert_url 指定当前工作目录绝对路径 98 | Array $para 请求的数据 99 | String $input_charset 编码格式。默认值:空值 100 | 输出:String 远程输出的数据 101 | 102 | function getHttpResponseGET($url, $cacert_url) 103 | 功能:远程获取数据,GET模式 104 | 输入:String $url 指定URL完整路径地址 105 | String $cacert_url 指定当前工作目录绝对路径 106 | 输出:String 远程输出的数据 107 | 108 | function charsetEncode($input,$_output_charset ,$_input_charset) 109 | 功能:实现多种字符编码方式 110 | 输入:String $input 需要编码的字符串 111 | String $_output_charset 输出的编码格式 112 | String $_input_charset 输入的编码格式 113 | 输出:String 编码后的字符串 114 | 115 | function charsetDecode($input,$_input_charset ,$_output_charset) 116 | 功能:实现多种字符解码方式 117 | 输入:String $input 需要解码的字符串 118 | String $_output_charset 输出的解码格式 119 | String $_input_charset 输入的解码格式 120 | 输出:String 解码后的字符串 121 | 122 | ┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉ 123 | 124 | function md5Sign($prestr, $key) 125 | 功能:MD5签名 126 | 输入:String $prestr 待签名数据 127 | String $key 私钥 128 | 输出:String 签名结果 129 | 130 | function md5Verify($prestr, $sign, $key) 131 | 功能:MD5验签 132 | 输入:String $data 待签名数据 133 | String $sign 签名结果 134 | String $key 私钥 135 | 输出:bool 验证结果 136 | ┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉ 137 | 138 | alipay_notify.class.php 139 | 140 | function verifyNotify() 141 | 功能:对notify_url的认证 142 | 输出:Bool 验证结果:true/false 143 | 144 | function verifyReturn() 145 | 功能:对return_url的认证 146 | 输出:Bool 验证结果:true/false 147 | 148 | function getSignVeryfy($para_temp, $sign) 149 | 功能:获取返回时的签名验证结果 150 | 输入:Array $para_temp 通知返回来的参数数组 151 | String $sign 支付宝返回的签名结果 152 | 输出:Bool 获得签名验证结果 153 | 154 | function getResponse($notify_id) 155 | 功能:获取远程服务器ATN结果,验证返回URL 156 | 输入:String $notify_id 通知校验ID 157 | 输出:String 服务器ATN结果 158 | 159 | ┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉ 160 | 161 | alipay_submit.class.php 162 | 163 | function buildRequestMysign($para_sort) 164 | 功能:生成要请求给支付宝的参数数组 165 | 输入:Array $para_sort 已排序要签名的数组 166 | 输出:String 签名结果 167 | 168 | function buildRequestPara($para_temp) 169 | 功能:根据反馈回来的信息,生成签名结果 170 | 输入:Array $para_temp 请求前的参数数组 171 | 输出:String 要请求的参数数组 172 | 173 | function buildRequestParaToString($para_temp) 174 | 功能:根据反馈回来的信息,生成签名结果 175 | 输入:Array $para_temp 请求前的参数数组 176 | 输出:String 要请求的参数数组字符串 177 | 178 | function buildRequestForm($para_temp, $method, $button_name) 179 | 功能:建立请求,以表单HTML形式构造(默认) 180 | 输入:Array $para_temp 请求前的参数数组 181 | String $method 提交方式。两个值可选:post、get 182 | String $button_name 确认按钮显示文字 183 | 输出:String 提交表单HTML文本 184 | 185 | function buildRequestHttp($para_temp) 186 | 功能:建立请求,以模拟远程HTTP的POST请求方式构造并获取支付宝的处理结果 187 | 输入:Array $para_temp 请求前的参数数组 188 | 输出:String 支付宝处理结果 189 | 190 | function buildRequestHttpInFile($para_temp, $file_para_name, $file_name) 191 | 功能:建立请求,以模拟远程HTTP的POST请求方式构造并获取支付宝的处理结果,带文件上传功能 192 | 输入:Array $para_temp 请求参数数组 193 | String $file_para_name 文件类型的参数名 194 | String $file_name 文件完整绝对路径 195 | 输出:String 支付宝返回处理结果 196 | 197 | function query_timestamp() 198 | 功能:用于防钓鱼,调用接口query_timestamp来获取时间戳的处理函数 199 | 输出:String 时间戳字符串 200 | 201 | ┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉┉ 202 | 203 | 204 | ────────── 205 | 出现问题,求助方法 206 | ────────── 207 | 208 | 如果在集成支付宝接口时,有疑问或出现问题,可使用下面的链接,提交申请。 209 | https://b.alipay.com/support/helperApply.htm?action=supportHome 210 | 我们会有专门的技术支持人员为您处理 211 | 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 支付宝即时到账交易接口接口 5 | 6 | 7 | 144 | 145 | 146 |
147 | 155 |
156 |
    157 |
  1. 1、确认信息 →
  2. 158 |
  3. 2、点击确认 →
  4. 159 |
  5. 3、确认完成
  6. 160 |
161 |
162 |
163 |
164 |
165 |
商户订单号:
166 |
167 | * 168 | 169 | 商户网站订单系统中唯一订单号,必填 170 |
171 |
订单名称:
172 |
173 | * 174 | 175 | 必填 176 |
177 |
付款金额:
178 |
179 | * 180 | 181 | 必填 182 |
183 |
订单描述:
184 |
185 | * 186 | 187 | 188 |
189 |
商品展示地址:
190 |
191 | * 192 | 193 | 需以http://开头的完整路径,例如:http://www.xxx.com/myorder.html 194 |
195 |
196 |
197 | 200 |
201 |
202 |
203 |
204 | 212 |
213 | 214 | 215 | -------------------------------------------------------------------------------- /Alipay.php: -------------------------------------------------------------------------------- 1 | 1, 26 | // 传输协议 27 | 'transport' => 'http', 28 | // 编码方式 29 | 'input_charset' => 'utf-8', 30 | // 签名方法 31 | 'sign_type' => 'MD5', 32 | // 证书路径 33 | 'cacert' => './cacert.pem', 34 | //验签公钥地址 35 | 'public_key_path' => './alipay_public_key.pem', 36 | 37 | 'private_key_path' => '' 38 | // // 支付完成异步通知调用地址 39 | // 'notify_url' => 'http://'.$_SERVER['HTTP_HOST'].'/order/callback_alipay/notify', 40 | // // 支付完成同步返回地址 41 | // 'return_url' => 'http://'.$_SERVER['HTTP_HOST'].'/order/callback_alipay/return', 42 | // // 支付宝商家 ID 43 | // 'partner' => '2088xxxxxxxx', 44 | // // 支付宝商家 KEY 45 | // 'key' => 'xxxxxxxxxxxx', 46 | // // 支付宝商家注册邮箱 47 | // 'seller_email' => 'email@domain.com' 48 | ); 49 | 50 | private $is_mobile = FALSE; 51 | 52 | public $service = self::SERVICE; 53 | public $gateway = self::GATEWAY; 54 | 55 | /** 56 | * 配置 57 | * @param $config array 配置信息 58 | * @param null $type string 类型 wap app 59 | */ 60 | public function __construct($config, $type = null) { 61 | $this->config = array_merge($this->config, (array)$config); 62 | 63 | $this->is_mobile = (($type == 'wap' || $type === true) ? true : false); 64 | 65 | if ($this->is_mobile) { 66 | $this->gateway = self::GATEWAY_MOBILE; 67 | } 68 | 69 | if ($type == 'wap' || $type === true) { 70 | $this->service = self::SERVICE_WAP; 71 | } elseif ($type == 'app') { 72 | $this->service = self::SERVICE_APP; 73 | } 74 | } 75 | 76 | /** 77 | * 生成请求参数的签名 78 | * 79 | * @param $params 80 | * @return 81 | * 82 | */ 83 | function signParameters($params) { 84 | // 支付宝的签名串必须是未经过 urlencode 的字符串 85 | // 不清楚为何 PHP 5.5 里没有 http_build_str() 方法 86 | $paramStr = urldecode(http_build_query($params)); 87 | 88 | switch (strtoupper(trim($this->config['sign_type']))) { 89 | case "MD5" : 90 | $result = md5($paramStr . $this->config['key']); 91 | break; 92 | 93 | case "RSA" : 94 | case "0001" : 95 | $priKey = file_get_contents($this->config['private_key_path']); 96 | $res = openssl_get_privatekey($priKey); 97 | openssl_sign($paramStr, $sign, $res); 98 | openssl_free_key($res); 99 | //base64编码 100 | $result = base64_encode($sign); 101 | break; 102 | 103 | default : 104 | $result = ""; 105 | } 106 | 107 | return $result; 108 | } 109 | 110 | /** 111 | * 准备签名参数 112 | * 113 | * @param $params 114 | * $params['out_trade_no'] 唯一订单编号 115 | * $params['subject'] 116 | * $params['total_fee'] 117 | * $params['body'] 118 | * $params['show_url'] 119 | * $params['anti_phishing_key'] 120 | * $params['exter_invoke_ip'] 121 | * $params['it_b_pay'] 122 | * $params['_input_charset'] 123 | * @return 124 | */ 125 | function prepareParameters($params) { 126 | $default = array( 127 | 'service' => $this->service, 128 | 'partner' => $this->config['partner'], 129 | '_input_charset' => trim(strtolower($this->config['input_charset'])) 130 | ); 131 | 132 | if (!$this->is_mobile) { 133 | $default = array_merge($default, array( 134 | 'payment_type' => $this->config['payment_type'], 135 | 'seller_id' => $this->config['partner'], 136 | 'notify_url' => $this->config['notify_url'], 137 | )); 138 | if (isset($this->config['return_url'])) { 139 | $default['return_url'] = $this->config['return_url']; 140 | } 141 | } 142 | 143 | $params = $this->filterSignParameter(array_merge($default, (array)$params)); 144 | ksort($params); 145 | reset($params); 146 | 147 | return $params; 148 | } 149 | 150 | /** 151 | * 生成签名后的请求参数 152 | * 153 | */ 154 | function buildSignedParameters($params) { 155 | $params = $this->prepareParameters($params); 156 | 157 | $params['sign'] = $this->signParameters($params); 158 | if ($params['service'] != self::SERVICE_WAP && $params['service'] != self::SERVICE_WAP_AUTH) { 159 | $params['sign_type'] = strtoupper(trim($this->config['sign_type'])); 160 | } 161 | 162 | return $params; 163 | } 164 | 165 | /** 166 | * https://doc.open.alipay.com/doc2/detail.htm?spm=a219a.7629140.0.0.NgdeQA&treeId=59&articleId=103663&docType=1 167 | * 服务端生成app支付使用的参数以及签名 168 | * @param $params 169 | * @return 170 | */ 171 | function buildSignedParametersForApp($params) { 172 | $params = $this->prepareParameters($params); 173 | 174 | $params['sign'] = urlencode($this->signParameters($params)); 175 | $params['sign_type'] = 'RSA'; 176 | 177 | $paramStr = []; 178 | foreach ($params as $k => &$param) { 179 | $param = '"' . $param . '"'; 180 | $paramStr[] = $k . '=' . $param; 181 | } 182 | 183 | return implode('&', $paramStr); 184 | } 185 | 186 | /** 187 | * 生成请求参数的发送表单HTML 188 | * 189 | * 其实这个函数没有必要,更应该使用签名后的参数自己组装,只不过有时候方便就从官方 SDK 里留下了。 190 | * 191 | * @param $params 请求参数(未签名的) 192 | * @param $method 请求方法,默认:post,可选 get 193 | * @param $target 提交目标,默认:_self 194 | * @return 195 | * 196 | */ 197 | function buildRequestFormHTML($params, $method = 'post', $target = '_self') { 198 | $params = $this->buildSignedParameters($params); 199 | $html = '
config['input_charset'])) . '" method="' . $method . ' target="$target">'; 200 | 201 | foreach ($params as $key => $value) { 202 | $html .= ""; 203 | } 204 | 205 | $html .= "
"; 206 | 207 | return $html; 208 | } 209 | 210 | /** 211 | * 准备移动网页支付的请求参数 212 | * 213 | * 移动网页支付接口不同,需要先服务器提交一次请求,拿到返回 token 再返回客户端发起真实支付请求。 214 | * 该方法只完成第一次服务端请求,生成参数后需要客户端另行处理(可调用`buildRequestFormHTML`生成表单提交)。 215 | * 216 | * @param $params 217 | * $params['out_trade_no'] 订单唯一编号 218 | * $params['subject'] 商品标题 219 | * $params['total_fee'] 支付总费用 220 | * $params['merchant_url'] 商品链接地址 221 | * $params['req_id'] 请求唯一 ID 222 | * $params['it_b_pay'] 超期时间(秒) 223 | * @return / 224 | */ 225 | function prepareMobileTradeData($params) { 226 | // 不要用 SimpleXML 来构建 xml 结构,因为有第一行文档申明支付宝验证不通过 227 | $xml_str = '' . 228 | '' . $this->config['notify_url'] . '' . 229 | '' . $this->config['return_url'] . '' . 230 | '' . $this->config['seller_email'] . '' . 231 | 232 | '' . $params['out_trade_no'] . '' . 233 | '' . htmlspecialchars($params['subject'], ENT_XML1, 'UTF-8') . '' . 234 | '' . $params['total_fee'] . '' . 235 | '' . $params['merchant_url'] . '' . 236 | (isset($params['it_b_pay']) ? '' . $params['it_b_pay'] . '' : '') . 237 | ''; 238 | 239 | $request_data = $this->buildSignedParameters(array( 240 | 'service' => $this->service, 241 | 'partner' => $this->config['partner'], 242 | 'sec_id' => $this->config['sign_type'], 243 | 'format' => 'xml', 244 | 'v' => '2.0', 245 | 246 | 'req_id' => $params['req_id'], 247 | 'req_data' => $xml_str 248 | )); 249 | 250 | $url = $this->gateway; 251 | $input_charset = trim(strtolower($this->config['input_charset'])); 252 | 253 | if (trim($input_charset) != '') { 254 | $url = $url . "_input_charset=" . $input_charset; 255 | } 256 | $curl = curl_init($url); 257 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);//SSL证书认证 258 | curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);//严格认证 259 | curl_setopt($curl, CURLOPT_CAINFO, $this->config['cacert']);//证书地址 260 | curl_setopt($curl, CURLOPT_HEADER, 0); // 过滤HTTP头 261 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);// 显示输出结果 262 | curl_setopt($curl, CURLOPT_POST, true); // post传输数据 263 | curl_setopt($curl, CURLOPT_POSTFIELDS, $request_data);// post传输数据 264 | $responseText = curl_exec($curl); 265 | //var_dump( curl_error($curl) );//如果执行curl过程中出现异常,可打开此开关,以便查看异常内容 266 | curl_close($curl); 267 | 268 | if (empty($responseText)) { 269 | return NULL; 270 | } 271 | 272 | parse_str($responseText, $responseData); 273 | 274 | if (empty($responseData['res_data'])) { 275 | return NULL; 276 | } 277 | 278 | if ($this->config['sign_type'] == '0001') { 279 | $responseData['res_data'] = $this->rsaDecrypt($responseData['res_data'], $this->config['private_key_path']); 280 | } 281 | 282 | //token从res_data中解析出来(也就是说res_data中已经包含token的内容) 283 | $doc = new \DOMDocument(); 284 | $doc->loadXML($responseData['res_data']); 285 | $responseData['request_token'] = $doc->getElementsByTagName("request_token")->item(0)->nodeValue; 286 | 287 | $xml_str = '' . 288 | '' . $responseData['request_token'] . '' . 289 | ''; 290 | 291 | return array( 292 | 'service' => self::SERVICE_WAP_AUTH, 293 | 'partner' => $this->config['partner'], 294 | 'sec_id' => $this->config['sign_type'], 295 | 'format' => 'xml', 296 | 'v' => '2.0', 297 | 'req_data' => $xml_str 298 | ); 299 | } 300 | 301 | /** 302 | * 支付完成验证返回参数(包含同步和异步) 303 | * 304 | * @return 305 | */ 306 | function verifyCallback() { 307 | $async = empty($_GET); 308 | 309 | $data = $async ? $_POST : $_GET; 310 | if (empty($data)) { 311 | return FALSE; 312 | } 313 | 314 | $signValid = $this->verifyParameters($data, $data["sign"]); 315 | $notify_id = isset($data['notify_id']) ? $data['notify_id'] : NULL; 316 | if ($async && $this->is_mobile) { 317 | //对notify_data解密 318 | if ($this->config['sign_type'] == '0001') { 319 | $data['notify_data'] = $this->rsaDecrypt($data['notify_data'], $this->config['private_key_path']); 320 | } 321 | 322 | //notify_id从decrypt_post_para中解析出来(也就是说decrypt_post_para中已经包含notify_id的内容) 323 | $doc = new \DOMDocument(); 324 | $doc->loadXML($data['notify_data']); 325 | $notify_id = $doc->getElementsByTagName('notify_id')->item(0)->nodeValue; 326 | } 327 | //获取支付宝远程服务器ATN结果(验证是否是支付宝发来的消息) 328 | $responseTxt = 'true'; 329 | if (!empty($notify_id)) { 330 | $responseTxt = $this->verifyFromServer($notify_id); 331 | } 332 | //验证 333 | //$signValid的结果不是true,与安全校验码、请求时的参数格式(如:带自定义参数等)、编码格式有关 334 | //$responsetTxt的结果不是true,与服务器设置问题、合作身份者ID、notify_id一分钟失效有关 335 | return $signValid && preg_match("/true$/i", $responseTxt); 336 | } 337 | 338 | function verifyParameters($params, $sign) { 339 | $params = $this->filterSignParameter($params); 340 | 341 | if (isset($params['notify_data'])) { 342 | $params = array( 343 | 'service' => $params['service'], 344 | 'v' => $params['v'], 345 | 'sec_id' => $params['sec_id'], 346 | 'notify_data' => $params['notify_data'] 347 | ); 348 | } else { 349 | ksort($params); 350 | reset($params); 351 | } 352 | 353 | $content = urldecode(http_build_query($params)); 354 | 355 | switch (strtoupper(trim($this->config['sign_type']))) { 356 | case "MD5" : 357 | return md5($content . $this->config['key']) == $sign; 358 | 359 | case "RSA" : 360 | case "0001" : 361 | return $this->rsaVerify($content, $this->config['public_key_path'], $sign); 362 | 363 | default : 364 | return FALSE; 365 | } 366 | } 367 | 368 | /** 369 | * 过滤参数,去除sign/sign_type参数 370 | * @param $params 371 | * @return 372 | */ 373 | function filterSignParameter($params) { 374 | $result = array(); 375 | foreach ($params as $key => $value) { 376 | if ($key != 'sign' && $key != 'sign_type' && $value) { 377 | $result[$key] = $value; 378 | } 379 | } 380 | return $result; 381 | } 382 | 383 | function verifyFromServer($notify_id) { 384 | $transport = strtolower(trim($this->config['transport'])); 385 | $partner = trim($this->config['partner']); 386 | $veryfy_url = ($transport == 'https' ? self::VERIFY_URL_HTTPS : self::VERIFY_URL) . "partner=$partner¬ify_id=$notify_id"; 387 | $curl = curl_init($veryfy_url); 388 | curl_setopt($curl, CURLOPT_HEADER, 0); // 过滤HTTP头 389 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);//SSL证书认证 390 | curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);//严格认证 391 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);// 显示输出结果 392 | curl_setopt($curl, CURLOPT_CAINFO, $this->config['cacert']);//证书地址 393 | $responseText = curl_exec($curl); 394 | // var_dump( curl_error($curl) );//如果执行curl过程中出现异常,可打开此开关,以便查看异常内容 395 | curl_close($curl); 396 | return $responseText; 397 | } 398 | 399 | /** 400 | * RSA验签,注意验签的公钥是支付宝的公钥,不是自己生成的rsa公钥,可以在淘宝的demo中获得 401 | * @param $data string 待签名数据 402 | * @param $ali_public_key_path string 支付宝的公钥文件路径 403 | * @param $sign string 要校对的的签名结果 404 | * @return 验证结果 405 | * @throws Exception 406 | */ 407 | function rsaVerify($data, $ali_public_key_path, $sign) { 408 | $pubKey = file_get_contents($ali_public_key_path); 409 | $res = openssl_get_publickey($pubKey); 410 | if(!$res){ 411 | throw new Exception('公钥格式错误'); 412 | } 413 | $result = (bool)openssl_verify($data, base64_decode($sign), $res); 414 | openssl_free_key($res); 415 | return $result; 416 | } 417 | 418 | /** 419 | * RSA解密 420 | * @param $content string 需要解密的内容,密文 421 | * @param $private_key_path string 商户私钥文件路径 422 | * @return string 解密后内容,明文 423 | */ 424 | function rsaDecrypt($content, $private_key_path) { 425 | $priKey = file_get_contents($private_key_path); 426 | $res = openssl_get_privatekey($priKey); 427 | //用base64将内容还原成二进制 428 | $content = base64_decode($content); 429 | //把需要解密的内容,按128位拆开解密 430 | $result = ''; 431 | for ($i = 0; $i < strlen($content) / 128; $i++) { 432 | $data = substr($content, $i * 128, 128); 433 | openssl_private_decrypt($data, $decrypt, $res); 434 | $result .= $decrypt; 435 | } 436 | openssl_free_key($res); 437 | return $result; 438 | } 439 | } 440 | --------------------------------------------------------------------------------