1.开发环境
Thinkphp 3.2.3
微信:服务号,已认证
开发域名:http://test.paywechat.com (自定义的域名,外网不可访问)
2.需要相关文件和权限
微信支付需申请开通
微信公众平台开发者文档:http://mp.weixin.qq.com/wiki/home/index.html
微信支付开发者文档:https://pay.weixin.qq.com/wiki/doc/api/index.html
微信支付SDK下载地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1
3.开发
下载好微信支付PHP版本的SDK,文件目录为下图:
现在介绍微信支付授权目录问题,首先是微信支付开发配置里面的支付授权目录填写,
然后填写JS接口安全域。
这些设置完,基本完成一半,注意设置的目录和我thinkphp里面的目录。
4.微信支付配置
现在开始贴出代码:
namespace
Wechat\Controller;
use
Think\Controller;
class
ParentController
extends
Controller {
protected
$options
=
array
(
'token'
=>
''
,
// 填写你设定的key
'encodingaeskey'
=>
''
,
// 填写加密用的EncodingAESKey
'appid'
=>
''
,
// 填写高级调用功能的app id
'appsecret'
=>
''
,
// 填写高级调用功能的密钥
'debug'
=> false,
'logcallback'
=>
''
);
public
$errCode
= 40001;
public
$errMsg
=
"no access"
;
/**
* 获取access_token
* @return mixed|boolean|unknown
*/
public
function
getToken(){
$cache_token
= S(
'exp_wechat_pay_token'
);
if
(!
empty
(
$cache_token
)){
return
$cache_token
;
}
$url
= sprintf(
$url
,
$this
->options[
'appid'
],
$this
->options[
'appsecret'
]);
$result
=
$this
->http_get(
$url
);
$result
= json_decode(
$result
,true);
if
(
empty
(
$result
)){
return
false;
}
S(
'exp_wechat_pay_token'
,
$result
[
'access_token'
],
array
(
'type'
=>
'file'
,
'expire'
=>3600));
return
$result
[
'access_token'
];
}
/**
* 发送客服消息
* @param array $data 消息结构{"touser":"OPENID","msgtype":"news","news":{...}}
*/
public
function
sendCustomMessage(
$data
){
$token
=
$this
->getToken();
if
(
empty
(
$token
))
return
false;
$url
= sprintf(
$url
,
$token
);
$result
=
$this
->http_post(
$url
,self::json_encode(
$data
));
if
(
$result
)
{
$json
= json_decode(
$result
,true);
if
(!
$json
|| !
empty
(
$json
[
'errcode'
])) {
$this
->errCode =
$json
[
'errcode'
];
$this
->errMsg =
$json
[
'errmsg'
];
return
false;
}
return
$json
;
}
return
false;
}
/**
* 发送模板消息
* @param unknown $data
* @return boolean|unknown
*/
public
function
sendTemplateMessage(
$data
){
$token
=
$this
->getToken();
if
(
empty
(
$token
))
return
false;
$url
= sprintf(
$url
,
$token
);
$result
=
$this
->http_post(
$url
,self::json_encode(
$data
));
if
(
$result
)
{
$json
= json_decode(
$result
,true);
if
(!
$json
|| !
empty
(
$json
[
'errcode'
])) {
$this
->errCode =
$json
[
'errcode'
];
$this
->errMsg =
$json
[
'errmsg'
];
return
false;
}
return
$json
;
}
return
false;
}
public
function
getFileCache(
$name
){
return
S(
$name
);
}
/**
* 微信api不支持中文转义的json结构
* @param array $arr
*/
static
function
json_encode(
$arr
) {
$parts
=
array
();
$is_list
= false;
//Find out if the given array is a numerical array
$keys
=
array_keys
(
$arr
);
$max_length
=
count
(
$arr
) - 1;
if
((
$keys
[0] === 0) && (
$keys
[
$max_length
] ===
$max_length
)) {
//See if the first key is 0 and last key is length - 1
$is_list
= true;
for
(
$i
= 0;
$i
<
count
(
$keys
);
$i
++) {
//See if each key correspondes to its position
if
(
$i
!=
$keys
[
$i
]) {
//A key fails at position check.
$is_list
= false;
//It is an associative array.
break
;
}
}
}
foreach
(
$arr
as
$key
=>
$value
) {
if
(
is_array
(
$value
)) {
//Custom handling for arrays
if
(
$is_list
)
$parts
[] = self::json_encode (
$value
);
/* :RECURSION: */
else
$parts
[] =
'"'
.
$key
.
'":'
. self::json_encode (
$value
);
/* :RECURSION: */
}
else
{
$str
=
''
;
if
(!
$is_list
)
$str
=
'"'
.
$key
.
'":'
;
//Custom handling for multiple data types
if
(!
is_string
(
$value
) &&
is_numeric
(
$value
) &&
$value
<2000000000)
$str
.=
$value
;
//Numbers
elseif
(
$value
=== false)
$str
.=
'false'
;
//The booleans
elseif
(
$value
=== true)
$str
.=
'true'
;
else
$str
.=
'"'
.
addslashes
(
$value
) .
'"'
;
//All other things
// :TODO: Is there any more datatype we should be in the lookout for? (Object?)
$parts
[] =
$str
;
}
}
$json
= implode (
','
,
$parts
);
if
(
$is_list
)
return
'['
.
$json
.
']'
;
//Return numerical JSON
return
'{'
.
$json
.
'}'
;
//Return associative JSON
}
/**
+----------------------------------------------------------
* 生成随机字符串
+----------------------------------------------------------
* @param int $length 要生成的随机字符串长度
* @param string $type 随机码类型:0,数字+大小写字母;1,数字;2,小写字母;3,大写字母;4,特殊字符;-1,数字+大小写字母+特殊字符
+----------------------------------------------------------
* @return string
+----------------------------------------------------------
*/
static
public
function
randCode(
$length
= 5,
$type
= 2){
$arr
=
array
(1 =>
"0123456789"
, 2 =>
"abcdefghijklmnopqrstuvwxyz"
, 3 =>
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
, 4 =>
"~@#$%^&*(){}[]|"
);
if
(
$type
== 0) {
array_pop
(
$arr
);
$string
= implode(
""
,
$arr
);
}
elseif
(
$type
==
"-1"
) {
$string
= implode(
""
,
$arr
);
}
else
{
$string
=
$arr
[
$type
];
}
$count
=
strlen
(
$string
) - 1;
$code
=
''
;
for
(
$i
= 0;
$i
<
$length
;
$i
++) {
$code
.=
$string
[rand(0,
$count
)];
}
return
$code
;
}
/**
* GET 请求
* @param string $url
*/
private
function
http_get(
$url
){
$oCurl
= curl_init();
curl_setopt(
$oCurl
, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt(
$oCurl
, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt(
$oCurl
, CURLOPT_SSLVERSION, 1);
//CURL_SSLVERSION_TLSv1
}
curl_setopt(
$oCurl
, CURLOPT_URL,
$url
);
curl_setopt(
$oCurl
, CURLOPT_RETURNTRANSFER, 1 );
$sContent
= curl_exec(
$oCurl
);
$aStatus
= curl_getinfo(
$oCurl
);
curl_close(
$oCurl
);
if
(
intval
(
$aStatus
[
"http_code"
])==200){
return
$sContent
;
}
else
{
return
false;
}
}
/**
* POST 请求
* @param string $url
* @param array $param
* @param boolean $post_file 是否文件上传
* @return string content
*/
private
function
http_post(
$url
,
$param
,
$post_file
=false){
$oCurl
= curl_init();
curl_setopt(
$oCurl
, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt(
$oCurl
, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt(
$oCurl
, CURLOPT_SSLVERSION, 1);
//CURL_SSLVERSION_TLSv1
}
if
(
is_string
(
$param
) ||
$post_file
) {
$strPOST
=
$param
;
}
else
{
$aPOST
=
array
();
foreach
(
$param
as
$key
=>
$val
){
$aPOST
[] =
$key
.
"="
.urlencode(
$val
);
}
$strPOST
= join(
"&"
,
$aPOST
);
}
curl_setopt(
$oCurl
, CURLOPT_URL,
$url
);
curl_setopt(
$oCurl
, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt(
$oCurl
, CURLOPT_POST,true);
curl_setopt(
$oCurl
, CURLOPT_POSTFIELDS,
$strPOST
);
$sContent
= curl_exec(
$oCurl
);
$aStatus
= curl_getinfo(
$oCurl
);
curl_close(
$oCurl
);
if
(
intval
(
$aStatus
[
"http_code"
])==200){
return
$sContent
;
}
else
{
return
false;
}
}
}
namespace
Wechat\Controller;
use
Wechat\Controller\ParentController;
/**
* 微信支付测试控制器
* @file TestController.class.php
* @author Gary <lizhiyong2204@sina.com>
* @date 2015年8月4日
* @todu
*/
class
TestController
extends
ParentController {
private
$_order_body
=
'xxx'
;
private
$_order_goods_tag
=
'xxx'
;
public
function
__construct(){
parent::__construct();
require_once
ROOT_PATH.
"Api/lib/WxPay.Api.php"
;
require_once
ROOT_PATH.
"Api/lib/WxPay.JsApiPay.php"
;
}
public
function
index(){
//①、获取用户openid
$tools
=
new
\JsApiPay();
$openId
=
$tools
->GetOpenid();
//②、统一下单
$input
=
new
\WxPayUnifiedOrder();
//商品描述
$input
->SetBody(
$this
->_order_body);
//附加数据,可以添加自己需要的数据,微信回异步回调时会附加这个数据
$input
->SetAttach(
'xxx'
);
//商户订单号
$out_trade_no
= \WxPayConfig::MCHID.
date
(
"YmdHis"
);
$input
->SetOut_trade_no(
$out_trade_no
);
//总金额,订单总金额,只能为整数,单位为分
$input
->SetTotal_fee(1);
//交易起始时间
$input
->SetTime_start(
date
(
"YmdHis"
));
//交易结束时间
$input
->SetTime_expire(
date
(
"YmdHis"
, time() + 600));
//商品标记
$input
->SetGoods_tag(
$this
->_order_goods_tag);
//通知地址,接收微信支付异步通知回调地址 SITE_URL=http://test.paywechat.com/Charge
$notify_url
= SITE_URL.
'/index.php/Test/notify.html'
;
$input
->SetNotify_url(
$notify_url
);
//交易类型
$input
->SetTrade_type(
"JSAPI"
);
$input
->SetOpenid(
$openId
);
$order
= \WxPayApi::unifiedOrder(
$input
);
$jsApiParameters
=
$tools
->GetJsApiParameters(
$order
);
//获取共享收货地址js函数参数
$editAddress
=
$tools
->GetEditAddressParameters();
$this
->assign(
'openId'
,
$openId
);
$this
->assign(
'jsApiParameters'
,
$jsApiParameters
);
$this
->assign(
'editAddress'
,
$editAddress
);
$this
->display();
}
/**
* 异步通知回调方法
*/
public
function
notify(){
require_once
ROOT_PATH.
"Api/lib/notify.php"
;
$notify
=
new
\PayNotifyCallBack();
$notify
->Handle(false);
//这里的IsSuccess是我自定义的一个方法,后面我会贴出这个文件的代码,供参考。
$is_success
=
$notify
->IsSuccess();
$bdata
=
$is_success
[
'data'
];
//支付成功
if
(
$is_success
[
'code'
] == 1){
$news
=
array
(
'touser'
=>
$bdata
[
'openid'
],
'msgtype'
=>
'news'
,
'news'
=>
array
(
'articles'
=>
array
(
array
(
'title'
=>
'订单支付成功'
,
'description'
=>
"支付金额:{$bdata['total_fee']}\n"
.
"微信订单号:{$bdata['transaction_id']}\n"
'picurl'
=>
''
,
'url'
=>
''
)
)
)
);
//发送微信支付通知
$this
->sendCustomMessage(
$news
);
}
else
{
//支付失败
}
}
/**
* 支付成功页面
* 不可靠的回调
*/
public
function
ajax_PaySuccess(){
//订单号
$out_trade_no
= I(
'post.out_trade_no'
);
//支付金额
$total_fee
= I(
'post.total_fee'
);
/*相关逻辑处理*/
}
贴上模板HTML<
html
>
<
head
>
<
meta
http-equiv
=
"content-type"
content
=
"text/html;charset=utf-8"
/>
<
meta
name
=
"viewport"
content
=
"width=device-width, initial-scale=1"
/>
<
title
>微信支付样例-支付</
title
>
<
script
type
=
"text/javascript"
>
//调用微信JS api 支付
function jsApiCall()
{
WeixinJSBridge.invoke(
'getBrandWCPayRequest',
{$jsApiParameters},
function(res){
WeixinJSBridge.log(res.err_msg);
//取消支付
if(res.err_msg == 'get_brand_wcpay_request:cancel'){
//处理取消支付的事件逻辑
}else if(res.err_msg == "get_brand_wcpay_request:ok"){
/*使用以上方式判断前端返回,微信团队郑重提示:
res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
这里可以使用Ajax提交到后台,处理一些日志,如Test控制器里面的ajax_PaySuccess方法。
*/
}
alert(res.err_code+res.err_desc+res.err_msg);
}
);
}
function callpay()
{
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', jsApiCall, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', jsApiCall);
document.attachEvent('onWeixinJSBridgeReady', jsApiCall);
}
}else{
jsApiCall();
}
}
//获取共享地址
function editAddress()
{
WeixinJSBridge.invoke(
'editAddress',
{$editAddress},
function(res){
var value1 = res.proviceFirstStageName;
var value2 = res.addressCitySecondStageName;
var value3 = res.addressCountiesThirdStageName;
var value4 = res.addressDetailInfo;
var tel = res.telNumber;
alert(value1 + value2 + value3 + value4 + ":" + tel);
}
);
}
window.onload = function(){
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', editAddress, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', editAddress);
document.attachEvent('onWeixinJSBridgeReady', editAddress);
}
}else{
editAddress();
}
};
</
script
>
</
head
>
<
body
>
<
br
/>
<
font
color
=
"#9ACD32"
><
b
>该笔订单支付金额为<
span
style
=
"color:#f00;font-size:50px"
>1分</
span
>钱</
b
></
font
><
br
/><
br
/>
<
div
align
=
"center"
>
<
button
style
=
"width:210px; height:50px; border-radius: 15px;background-color:#FE6714; border:0px #FE6714 solid; cursor: pointer; color:white; font-size:16px;"
type
=
"button"
onclick
=
"callpay()"
>立即支付</
button
>
</
div
>
</
body
>
</
html
>
notify.php文件代码,这里有在官方文件里新添加的一个自定义方法。require_once
ROOT_PATH.
"Api/lib/WxPay.Api.php"
;
require_once
ROOT_PATH.
'Api/lib/WxPay.Notify.php'
;
require_once
ROOT_PATH.
'Api/lib/log.php'
;
//初始化日志
$logHandler
=
new
\CLogFileHandler(ROOT_PATH.
"/logs/"
.
date
(
'Y-m-d'
).
'.log'
);
$log
= \Log::Init(
$logHandler
, 15);
class
PayNotifyCallBack
extends
WxPayNotify
{
protected
$para
=
array
(
'code'
=>0,
'data'
=>
''
);
//查询订单
public
function
Queryorder(
$transaction_id
)
{
$input
=
new
\WxPayOrderQuery();
$input
->SetTransaction_id(
$transaction_id
);
$result
= \WxPayApi::orderQuery(
$input
);
\Log::DEBUG(
"query:"
. json_encode(
$result
));
if
(
array_key_exists
(
"return_code"
,
$result
)
&&
array_key_exists
(
"result_code"
,
$result
)
&&
$result
[
"return_code"
] ==
"SUCCESS"
&&
$result
[
"result_code"
] ==
"SUCCESS"
)
{
return
true;
}
$this
->para[
'code'
] = 0;
$this
->para[
'data'
] =
''
;
return
false;
}
//重写回调处理函数
public
function
NotifyProcess(
$data
, &
$msg
)
{
\Log::DEBUG(
"call back:"
. json_encode(
$data
));
$notfiyOutput
=
array
();
if
(!
array_key_exists
(
"transaction_id"
,
$data
)){
$msg
=
"输入参数不正确"
;
$this
->para[
'code'
] = 0;
$this
->para[
'data'
] =
''
;
return
false;
}
//查询订单,判断订单真实性
if
(!
$this
->Queryorder(
$data
[
"transaction_id"
])){
$msg
=
"订单查询失败"
;
$this
->para[
'code'
] = 0;
$this
->para[
'data'
] =
''
;
return
false;
}
$this
->para[
'code'
] = 1;
$this
->para[
'data'
] =
$data
;
return
true;
}
/**
* 自定义方法 检测微信端是否回调成功方法
* @return multitype:number string
*/
public
function
IsSuccess(){
return
$this
->para;
}
}
=联系客服