1.開發(fā)環(huán)境
Thinkphp 3.2.3
微信:服務(wù)號,已認證
開發(fā)域名:http://test.paywechat.com (自定義的域名,外網(wǎng)不可訪問)
2.需要相關(guān)文件和權(quán)限
微信支付需申請開通
微信公眾平臺開發(fā)者文檔:http://mp.weixin.qq.com/wiki/home/index.html
微信支付開發(fā)者文檔: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.開發(fā)
下載好微信支付PHP版本的SDK,文件目錄為下圖:
現(xiàn)在介紹微信支付授權(quán)目錄問題,首先是微信支付開發(fā)配置里面的支付授權(quán)目錄填寫,
然后填寫JS接口安全域。
這些設(shè)置完,基本完成一半,注意設(shè)置的目錄和我thinkphp里面的目錄。
4.微信支付配置
現(xiàn)在開始貼出代碼:
namespace
Wechat\Controller;
use
Think\Controller;
class
ParentController
extends
Controller {
protected
$options
=
array
(
'token'
=>
''
,
// 填寫你設(shè)定的key
'encodingaeskey'
=>
''
,
// 填寫加密用的EncodingAESKey
'appid'
=>
''
,
// 填寫高級調(diào)用功能的app id
'appsecret'
=>
''
,
// 填寫高級調(diào)用功能的密鑰
'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'
];
}
/**
* 發(fā)送客服消息
* @param array $data 消息結(jié)構(gòu){"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;
}
/**
* 發(fā)送模板消息
* @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不支持中文轉(zhuǎn)義的json結(jié)構(gòu)
* @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,數(shù)字+大小寫字母;1,數(shù)字;2,小寫字母;3,大寫字母;4,特殊字符;-1,數(shù)字+大小寫字母+特殊字符
+----------------------------------------------------------
* @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();
//②、統(tǒng)一下單
$input
=
new
\WxPayUnifiedOrder();
//商品描述
$input
->SetBody(
$this
->_order_body);
//附加數(shù)據(jù),可以添加自己需要的數(shù)據(jù),微信回異步回調(diào)時會附加這個數(shù)據(jù)
$input
->SetAttach(
'xxx'
);
//商戶訂單號
$out_trade_no
= \WxPayConfig::MCHID.
date
(
"YmdHis"
);
$input
->SetOut_trade_no(
$out_trade_no
);
//總金額,訂單總金額,只能為整數(shù),單位為分
$input
->SetTotal_fee(1);
//交易起始時間
$input
->SetTime_start(
date
(
"YmdHis"
));
//交易結(jié)束時間
$input
->SetTime_expire(
date
(
"YmdHis"
, time() + 600));
//商品標記
$input
->SetGoods_tag(
$this
->_order_goods_tag);
//通知地址,接收微信支付異步通知回調(diào)地址 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函數(shù)參數(shù)
$editAddress
=
$tools
->GetEditAddressParameters();
$this
->assign(
'openId'
,
$openId
);
$this
->assign(
'jsApiParameters'
,
$jsApiParameters
);
$this
->assign(
'editAddress'
,
$editAddress
);
$this
->display();
}
/**
* 異步通知回調(diào)方法
*/
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'
=>
''
)
)
)
);
//發(fā)送微信支付通知
$this
->sendCustomMessage(
$news
);
}
else
{
//支付失敗
}
}
/**
* 支付成功頁面
* 不可靠的回調(diào)
*/
public
function
ajax_PaySuccess(){
//訂單號
$out_trade_no
= I(
'post.out_trade_no'
);
//支付金額
$total_fee
= I(
'post.total_fee'
);
/*相關(guān)邏輯處理*/
}
貼上模板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"
>
//調(diào)用微信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;
}
//重寫回調(diào)處理函數(shù)
public
function
NotifyProcess(
$data
, &
$msg
)
{
\Log::DEBUG(
"call back:"
. json_encode(
$data
));
$notfiyOutput
=
array
();
if
(!
array_key_exists
(
"transaction_id"
,
$data
)){
$msg
=
"輸入?yún)?shù)不正確"
;
$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;
}
/**
* 自定義方法 檢測微信端是否回調(diào)成功方法
* @return multitype:number string
*/
public
function
IsSuccess(){
return
$this
->para;
}
}
=