├── 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."".$key.">";
93 |
94 | }
95 | else
96 | $xml.="<".$key.">".$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 | ?>
--------------------------------------------------------------------------------