├── README.md ├── config.php ├── index.php ├── notify.php ├── tb_pay_log.sql ├── Wxpay.php └── WxPayHelper.php /README.md: -------------------------------------------------------------------------------- 1 | 微信支付APP服务器端代码逻辑处理 2 | 3 | 4 | index.php:微信APP支付(服务器端完成统一下单,将数据传输给APP,APP调起返回的支付信息完成支付) 5 | 6 | notify.php:微信APP支付异步通知,更改订单状态。 7 | 8 | config.php:微信appid配置文件 9 | 10 | WxPayHelper.php: 微信类方法文件 11 | 12 | Wxpay.php: 微信支付类 13 | 14 | tb_pay_log.sql:支付日志数据库文件 15 | 16 | 17 | 有问题可以发送到此邮箱:shangheguang#yeah.net 把#替换为@ 18 | -------------------------------------------------------------------------------- /config.php: -------------------------------------------------------------------------------- 1 | "wxxxxxx", /*微信开放平台上的应用id*/ 10 | 'mch_id' => "11111111", /*微信申请成功之后邮件中的商户id*/ 11 | 'api_key' => "shangheguang", /*在微信商户平台上自己设定的api密钥 32位*/ 12 | ); 13 | 14 | 15 | ?> -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 15 | wx2421b1c4370ec43b 16 | 支付测试 17 | APP支付测试 18 | 10000100 19 | 1add1a30ac87aa2db72f57a2375d8fec 20 | http://wxpay.weixin.qq.com/pub_v2/pay/notify.v2.php 21 | 1415659990 22 | 14.23.150.211 23 | 1 24 | APP 25 | 0CB01533B8C1EF103065174F50BCA001 26 | 27 | **/ 28 | require_once 'Wxpay.php'; 29 | 30 | $Wxpay = new Wxpay(); 31 | $total_fee = 1; //订单总金额 32 | $Wxpay->total_fee = intval($total_fee*100);//订单的金额 1元 33 | $Wxpay->out_trade_no = date('YmdHis') . substr(time(), - 5) . substr(microtime(), 2, 5) . sprintf('%02d', rand(0, 99));//订单号 34 | $Wxpay->body = '描述信息';//支付描述信息 35 | $Wxpay->time_expire = date('YmdHis', time()+86400);//订单支付的过期时间(eg:一天过期) 36 | $Wxpay->notify_url = 'http://www.baidu.cn/v1/wxpay/notify/';//异步通知URL(更改支付状态) 37 | 38 | //数据以JSON的形式返回给APP 39 | $app_response = $Wxpay->doPay(); 40 | if (isset($app_response['return_code']) && $app_response['return_code']=='FAIL') { 41 | $errorCode = 100; 42 | $errorMsg = $app_response['return_msg']; 43 | echoResult($errorCode, $errorMsg); 44 | } else { 45 | $errorCode = 0; 46 | $errorMsg = 'success'; 47 | $responseData = array( 48 | 'notify_url' => $Wxpay->notify_url, 49 | 'app_response' => $app_response, 50 | ); 51 | echoResult($errorCode, $errorMsg, $responseData); 52 | } 53 | 54 | //接口输出 55 | function echoResult($errorCode = 0, $errorMsg = 'success', $responseData = array()) 56 | { 57 | $arr = array( 58 | 'errorCode' => $errorCode, 59 | 'errorMsg' => $errorMsg, 60 | 'responseData' => $responseData, 61 | ); 62 | exit(json_encode($arr)); 63 | } 64 | 65 | -------------------------------------------------------------------------------- /notify.php: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 47200 28 | 29 | 30 | 31 | **/ 32 | require_once 'Wxpay.php'; 33 | 34 | $Wxpay = new Wxpay(); 35 | $verify_result = $Wxpay->verifyNotify(); 36 | if (isset($verify_result['result_code']) && $verify_result['result_code']=='SUCCESS') { 37 | $requestReturnData = file_get_contents("php://input"); 38 | //商户订单号 39 | $out_trade_no = $verify_result['out_trade_no']; 40 | //交易号 41 | $trade_no = $verify_result['transaction_id']; 42 | //交易状态 43 | $trade_status = $verify_result['result_code']; 44 | //支付金额 45 | $total_fee = $verify_result['total_fee']/100; 46 | //支付过期时间 47 | $pay_date = $verify_result['time_end']; 48 | //IP 49 | $pay_ip = $verify_result['attach']; 50 | /* 51 | @todo 52 | 1.更改订单状态为已支付。(需自己完善) 53 | 2.添加付款信息到数据库,方便对账。(需自己完善) 54 | */ 55 | $pay_arr = array( 56 | 'pay_type' => isset($_REQUEST['pay_type']) ? $_REQUEST['pay_type'] : '', 57 | 'action' => 'notify', 58 | 'domain_type' => isset($_REQUEST['domain_type']) ? $_REQUEST['domain_type'] : '', 59 | 'out_trade_no' => $out_trade_no, 60 | 'trade_no' => $trade_no, 61 | 'trade_status' => $trade_status, 62 | 'trade_return_data' => $requestReturnData, 63 | 'create_ip' => $pay_ip, 64 | ); 65 | //处理后同步返回给微信 66 | exit(''); 67 | } 68 | exit(''); 69 | 70 | 71 | -------------------------------------------------------------------------------- /tb_pay_log.sql: -------------------------------------------------------------------------------- 1 | -- phpMyAdmin SQL Dump 2 | -- version 4.6.0 3 | -- http://www.phpmyadmin.net 4 | -- 5 | -- Host: localhost 6 | -- Generation Time: 2016-07-19 06:16:09 7 | -- 服务器版本: 5.6.19 8 | -- PHP Version: 7.0.5 9 | 10 | SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; 11 | SET time_zone = "+00:00"; 12 | 13 | 14 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 15 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 16 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 17 | /*!40101 SET NAMES utf8mb4 */; 18 | 19 | -- 20 | -- Database: `test` 21 | -- 22 | 23 | -- -------------------------------------------------------- 24 | 25 | -- 26 | -- 表的结构 `tb_pay_log` 27 | -- 28 | 29 | CREATE TABLE `tb_pay_log` ( 30 | `id` bigint(20) UNSIGNED NOT NULL, 31 | `pay_type` tinyint(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT '支付渠道类型[1:支付宝,2微信]', 32 | `action` varchar(10) NOT NULL DEFAULT '' COMMENT '[return,notify]', 33 | `domain_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '来源域名(1www 2m 3ios 4android)', 34 | `out_trade_no` varchar(30) NOT NULL DEFAULT '' COMMENT '商家订单编号', 35 | `trade_no` varchar(30) NOT NULL DEFAULT '' COMMENT '平台交易号', 36 | `trade_status` varchar(30) NOT NULL DEFAULT '' COMMENT '交易状态', 37 | `trade_return_data` text NOT NULL COMMENT '平台返回数据', 38 | `create_ip` varchar(30) NOT NULL DEFAULT '' COMMENT '用户支付IP', 39 | `create_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '创建时间' 40 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 41 | 42 | -- 43 | -- 转存表中的数据 `tb_pay_log` 44 | -- 45 | 46 | INSERT INTO `tb_pay_log` (`id`, `pay_type`, `action`, `domain_type`, `out_trade_no`, `trade_no`, `trade_status`, `trade_return_data`, `create_ip`, `create_date`) VALUES 47 | (1, 2, 'notify', 3, '20160108230835657155974231', '1009880762201601082587876156', 'SUCCESS', '\n\n\n\n\n\n\n\n\n\n\n\n\n\n47200\n\n\n', '140.207.54.75', '2016-01-08 23:08:52'), 48 | (2, 1, 'notify', 3, '20151221230600103605339363', '2015122121001004100010912642', 'TRADE_FINISHED', 'discount=0.00&payment_type=1&subject=标题&trade_no=2015111821001004460078599104&buyer_email=243882284@qq.com&gmt_create=2015-12-18 22:01:37¬ify_type=trade_status_sync&quantity=1&out_trade_no=20151218220127472878962898&seller_id=2033901888618287¬ify_time=2016-03-18 22:02:34&body=主题&trade_status=TRADE_FINISHED&is_total_fee_adjust=N&total_fee=120.00&gmt_payment=2015-12-18 22:01:38&seller_email=shangheguang@yeah.com&gmt_close=2016-03-18 22:02:34&price=120.00&buyer_id=1288702326699464¬ify_id=e9063f932b7bc86b85366a5b7451a27jjs&use_coupon=N&sign_type=RSA&sign=ANpF7OIJHIatkrBpbrY1dQKQqKlK8PQOlbZiOQX5r19S5FE7Eb9ArC5tN5n19X2fIEtRcD9rLctM9F1uc4VAxMOt8j+DWZa+5YuBHGMuoA+0E3WDTBeAq59veLtbno1PL1vPWpEaz8zXbRH1Nt55XC3oTCR8Wfvdt2/eTbTAjLM=', '110.75.225.147', '2016-03-21 23:06:36'); 49 | 50 | -- 51 | -- Indexes for dumped tables 52 | -- 53 | 54 | -- 55 | -- Indexes for table `tb_pay_log` 56 | -- 57 | ALTER TABLE `tb_pay_log` 58 | ADD PRIMARY KEY (`id`); 59 | 60 | -- 61 | -- 在导出的表使用AUTO_INCREMENT 62 | -- 63 | 64 | -- 65 | -- 使用表AUTO_INCREMENT `tb_pay_log` 66 | -- 67 | ALTER TABLE `tb_pay_log` 68 | MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3; 69 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 70 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 71 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 72 | -------------------------------------------------------------------------------- /Wxpay.php: -------------------------------------------------------------------------------- 1 | config = $config; 36 | $this->WxPayHelper = new WxPayHelper(); 37 | } 38 | 39 | public function chkParam() 40 | { 41 | //用户网站订单号 42 | if (empty($this->out_trade_no)) { 43 | die('out_trade_no error'); 44 | } 45 | //商品描述 46 | if (empty($this->body)) { 47 | die('body error'); 48 | } 49 | if (empty($this->time_expire)){ 50 | die('time_expire error'); 51 | } 52 | //检测支付金额 53 | if (empty($this->total_fee) || !is_numeric($this->total_fee)) { 54 | die('total_fee error'); 55 | } 56 | //异步通知URL 57 | if (empty($this->notify_url)) { 58 | die('notify_url error'); 59 | } 60 | if (!preg_match("#^http:\/\/#i", $this->notify_url)) { 61 | $this->notify_url = "http://" . $_SERVER['HTTP_HOST'] . $this->notify_url; 62 | } 63 | return true; 64 | } 65 | 66 | /** 67 | * 生成支付(返回给APP) 68 | * @return boolean|mixed 69 | */ 70 | public function doPay() { 71 | //检测构造参数 72 | $this->chkParam(); 73 | return $this->createAppPara(); 74 | } 75 | 76 | /** 77 | * APP统一下单 78 | */ 79 | private function createAppPara() 80 | { 81 | $url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; 82 | 83 | $data["appid"] = $this->config['appid'];//微信开放平台审核通过的应用APPID 84 | $data["body"] = $this->body;//商品或支付单简要描述 85 | $data["mch_id"] = $this->config['mch_id'];//商户号 86 | $data["nonce_str"] = $this->WxPayHelper->getRandChar(32);//随机字符串 87 | $data["notify_url"] = $this->notify_url;//通知地址 88 | $data["out_trade_no"] = $this->out_trade_no;//商户订单号 89 | $data["spbill_create_ip"] = $this->WxPayHelper->get_client_ip();//终端IP 90 | $data["total_fee"] = $this->total_fee;//总金额 91 | $data["time_expire"] = $this->time_expire;//交易结束时间 92 | $data["trade_type"] = "APP";//交易类型 93 | $data["sign"] = $this->WxPayHelper->getSign($data, $this->config['api_key']);//签名 94 | 95 | $xml = $this->WxPayHelper->arrayToXml($data); 96 | $response = $this->WxPayHelper->postXmlCurl($xml, $url); 97 | 98 | //将微信返回的结果xml转成数组 99 | $responseArr = $this->WxPayHelper->xmlToArray($response); 100 | if(isset($responseArr["return_code"]) && $responseArr["return_code"]=='SUCCESS'){ 101 | return $this->getOrder($responseArr['prepay_id']); 102 | } 103 | return $responseArr; 104 | } 105 | 106 | /** 107 | * 执行第二次签名,才能返回给客户端使用 108 | * @param int $prepayId:预支付交易会话标识 109 | * @return array 110 | */ 111 | public function getOrder($prepayId) 112 | { 113 | $data["appid"] = $this->config['appid']; 114 | $data["noncestr"] = $this->WxPayHelper->getRandChar(32); 115 | $data["package"] = "Sign=WXPay"; 116 | $data["partnerid"] = $this->config['mch_id']; 117 | $data["prepayid"] = $prepayId; 118 | $data["timestamp"] = time(); 119 | $data["sign"] = $this->WxPayHelper->getSign($data, $this->config['api_key']); 120 | $data["packagestr"] = "Sign=WXPay"; 121 | return $data; 122 | } 123 | 124 | /** 125 | * 异步通知信息验证 126 | * @return boolean|mixed 127 | */ 128 | public function verifyNotify() 129 | { 130 | $xml = isset($GLOBALS['HTTP_RAW_POST_DATA']) ? $GLOBALS['HTTP_RAW_POST_DATA'] : ''; 131 | if(!$xml){ 132 | return false; 133 | } 134 | $wx_back = $this->WxPayHelper->xmlToArray($xml); 135 | if(empty($wx_back)){ 136 | return false; 137 | } 138 | $checkSign = $this->WxPayHelper->getVerifySign($wx_back, $this->config['api_key']); 139 | if($checkSign==$wx_back['sign']){ 140 | return $wx_back; 141 | } return false; 142 | } 143 | 144 | function __destruct() { 145 | 146 | } 147 | 148 | } 149 | 150 | -------------------------------------------------------------------------------- /WxPayHelper.php: -------------------------------------------------------------------------------- 1 | formatParameters($data, false); 14 | //签名步骤二:在string后加入KEY 15 | $String = $String . "&key=" . $key; 16 | //签名步骤三:MD5加密 17 | $String = md5($String); 18 | //签名步骤四:所有字符转为大写 19 | $result = strtoupper($String); 20 | return $result; 21 | } 22 | 23 | function formatParameters($paraMap, $urlencode) 24 | { 25 | $buff = ""; 26 | ksort($paraMap); 27 | foreach ($paraMap as $k => $v) { 28 | if($k=="sign"){ 29 | continue; 30 | } 31 | if ($urlencode) { 32 | $v = urlencode($v); 33 | } 34 | $buff .= $k . "=" . $v . "&"; 35 | } 36 | $reqPar; 37 | if (strlen($buff) > 0) { 38 | $reqPar = substr($buff, 0, strlen($buff) - 1); 39 | } 40 | return $reqPar; 41 | } 42 | 43 | /** 44 | * 得到签名 45 | * @param object $obj 46 | * @param string $api_key 47 | * @return string 48 | */ 49 | function getSign($obj, $api_key) 50 | { 51 | foreach ($obj as $k => $v) 52 | { 53 | $Parameters[strtolower($k)] = $v; 54 | } 55 | //签名步骤一:按字典序排序参数 56 | ksort($Parameters); 57 | $String = $this->formatBizQueryParaMap($Parameters, false); 58 | //签名步骤二:在string后加入KEY 59 | $String = $String."&key=".$api_key; 60 | //签名步骤三:MD5加密 61 | $result = strtoupper(md5($String)); 62 | return $result; 63 | } 64 | 65 | /** 66 | * 获取指定长度的随机字符串 67 | * @param int $length 68 | * @return Ambigous 69 | */ 70 | function getRandChar($length){ 71 | $str = null; 72 | $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"; 73 | $max = strlen($strPol)-1; 74 | for($i=0;$i<$length;$i++){ 75 | $str.=$strPol[rand(0,$max)];//rand($min,$max)生成介于min和max两个数之间的一个随机整数 76 | } 77 | return $str; 78 | } 79 | 80 | /** 81 | * 数组转xml 82 | * @param array $arr 83 | * @return string 84 | */ 85 | function arrayToXml($arr) 86 | { 87 | $xml = ""; 88 | foreach ($arr as $key=>$val) 89 | { 90 | if (is_numeric($val)) 91 | { 92 | $xml.="<".$key.">".$val.""; 93 | 94 | } 95 | else 96 | $xml.="<".$key.">"; 97 | } 98 | $xml.=""; 99 | return $xml; 100 | } 101 | 102 | /** 103 | * 以post方式提交xml到对应的接口url 104 | * 105 | * @param string $xml 需要post的xml数据 106 | * @param string $url url 107 | * @param bool $useCert 是否需要证书,默认不需要 108 | * @param int $second url执行超时时间,默认30s 109 | * @throws WxPayException 110 | */ 111 | function postXmlCurl($xml, $url, $second=30, $useCert=false, $sslcert_path='', $sslkey_path='') 112 | { 113 | $ch = curl_init(); 114 | //设置超时 115 | curl_setopt($ch, CURLOPT_TIMEOUT, $second); 116 | curl_setopt($ch,CURLOPT_URL, $url); 117 | 118 | //设置header 119 | curl_setopt($ch, CURLOPT_HEADER, FALSE); 120 | //要求结果为字符串且输出到屏幕上 121 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); 122 | curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE); 123 | curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE); 124 | 125 | if($useCert == true){ 126 | curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE); 127 | curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验 128 | //设置证书 129 | //使用证书:cert 与 key 分别属于两个.pem文件 130 | curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM'); 131 | curl_setopt($ch,CURLOPT_SSLCERT, $sslcert_path); 132 | curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM'); 133 | curl_setopt($ch,CURLOPT_SSLKEY, $sslkey_path); 134 | } 135 | //post提交方式 136 | curl_setopt($ch, CURLOPT_POST, TRUE); 137 | curl_setopt($ch, CURLOPT_POSTFIELDS, $xml); 138 | //运行curl 139 | $data = curl_exec($ch); 140 | 141 | //返回结果 142 | if($data){ 143 | curl_close($ch); 144 | return $data; 145 | } else { 146 | $error = curl_errno($ch); 147 | 148 | curl_close($ch); 149 | return false; 150 | } 151 | } 152 | 153 | /** 154 | * 获取当前服务器的IP 155 | * @return Ambigous 156 | */ 157 | function get_client_ip() 158 | { 159 | if (isset($_SERVER['REMOTE_ADDR'])) { 160 | $cip = $_SERVER['REMOTE_ADDR']; 161 | } elseif (getenv("REMOTE_ADDR")) { 162 | $cip = getenv("REMOTE_ADDR"); 163 | } elseif (getenv("HTTP_CLIENT_IP")) { 164 | $cip = getenv("HTTP_CLIENT_IP"); 165 | } else { 166 | $cip = "127.0.0.1"; 167 | } 168 | return $cip; 169 | } 170 | 171 | /** 172 | * 将数组转成uri字符串 173 | * @param array $paraMap 174 | * @param bool $urlencode 175 | * @return string 176 | */ 177 | function formatBizQueryParaMap($paraMap, $urlencode) 178 | { 179 | $buff = ""; 180 | ksort($paraMap); 181 | foreach ($paraMap as $k => $v) 182 | { 183 | if($urlencode) 184 | { 185 | $v = urlencode($v); 186 | } 187 | $buff .= strtolower($k) . "=" . $v . "&"; 188 | } 189 | $reqPar; 190 | if (strlen($buff) > 0) 191 | { 192 | $reqPar = substr($buff, 0, strlen($buff)-1); 193 | } 194 | return $reqPar; 195 | } 196 | 197 | /** 198 | * XML转数组 199 | * @param unknown $xml 200 | * @return mixed 201 | */ 202 | function xmlToArray($xml) 203 | { 204 | //将XML转为array 205 | $array_data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); 206 | return $array_data; 207 | } 208 | 209 | } 210 | 211 | 212 | ?> --------------------------------------------------------------------------------