微信内网页开发 - 公众号支付
接口文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_4
开发步骤:
一、设置支付授权目录
二、流程
1、前端H5页面请求服务端生成唯一订单号(包括用户信息,支付金额,商品信息等),服务端在数据库创建一条新记录
2、前端H5页面请求服务端Perl CGI脚本进行支付: 例如https:/xxxx/cgi-bin/pay.pl?do=jspay&order_id=xxxxxxx&openid=xxxxxx
////脚本处理
if ($cgi->param('do') eq "jspay") {
}
3、CGI脚本调用接口访问数据库,获取支付记录信息,调用微信统一下单接口
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
其中notify_url直接填本CGI脚本文件的地址,通知url必须为直接可访问的url,不能携带参数。示例:notify_url:"https:/xxxx/cgi-bin/pay.pl"
use CGI;
use warnings;
use JSON;
use utf8;
use Digest::MD5 qw/md5_hex/;
use HTTP::Request;
use HTTP::Headers;
use LWP::UserAgent;
use Encode;
use XML::Simple;
use Data::Dumper;
if ($cgi->param('do') eq "jspay") {
my $order_id = $cgi->param('order_id');
my $openid = $cgi->param('openid');
my $order_info = get_order_by_id($order_id);
my $wx_order_info;
$wx_order_info->{attach}="$order_id"; #order_id用于后面更新服务器数据
$wx_order_info->{body}="Happy New Year";
$wx_order_info->{mch_id}=$MCH_ID;
$wx_order_info->{nonce_str}=nonce_str();
$wx_order_info->{openid}=$openid;
$wx_order_info->{trade_type}="JSAPI";
$wx_order_info->{out_trade_no}=$order_info->{_id};
$wx_order_info->{total_fee}=100*$order_info->{rmb};
$wx_order_info->{notify_url}="http://xxxxx/cgi-bin/pay.pl";
$wx_order_info->{spbill_create_ip}="127.0.0.1";
$wx_order_info->{sign_type}="MD5";
$wx_order_info->{sign} = sign($wx_order_info); #签名
my $request_xml = create_xml_data($wx_order_info); #转成XML格式
#发送POST请求 统一下单接口
my $header = HTTP::Headers->new( Content_Type => 'text/xml; charset=utf8', );
my $http_request = HTTP::Request->new( POST => "https://api.mch.weixin.qq.com/pay/unifiedorder", $header, $request_xml );
my $ua = LWP::UserAgent->new(ssl_opts => { verify_hostname => 0, SSL_verify_mode => 0x00 });
#解析响应
my $response = $ua->request($http_request);
my $response_json;
if ($response->message ne "OK" && $response->is_success ne "1") { #出错,或者timeout了
$response_json->{status} = "99999";
$response_json->{message} = $response->message;
$response_json->{is_success} = $response->is_success;
} else {
$response_json = parse_xml_response( $response->content());
}
}
sub create_xml_data {
my $params = $_[0];
my $xml = '<xml>';
foreach ( keys %$params ) {
if ( $params->{$_} and $params->{$_} !~ /^\d+$/ ) {
$xml .= sprintf( '<%s><![CDATA[%s]]></%s>', $_, $params->{$_}, $_ );
} else {
$xml .= sprintf( '<%s>%s</%s>', $_, $params->{$_}, $_ );
}
}
$xml .= '</xml>';
return $xml;
}
sub sign {
my $params = $_[0];
my $app_key = $APP_KEY;
my $params_sign = {};
foreach ( keys %$params ) {
next if $_ eq 'sign';
next unless defined $params->{$_};
Encode::_utf8_off( $params->{$_} );
$params_sign->{$_} = $params->{$_};
}
my $sign_string = join( '&',
map { sprintf( '%s=%s', $_, $params_sign->{$_} ) }
sort { $a cmp $b } keys %$params_sign );
$sign_string .= sprintf( '&key=%s', $app_key );
return uc md5_hex $sign_string;
}
sub parse_xml_response {
my $content = $_[0];
my $result = XMLin($content);
return $result;
}
4、CGI脚本根据统一下单接口返回的参数,调用jsapi进行支付
5、支付成功或者失败分别重定向到响应的H5页面
{
my $pay_info = $json->decode($response_json->{pay_info});
my $js_obj;
$js_obj->{appId} = $pay_info->{appId};
$js_obj->{timeStamp} = $pay_info->{timeStamp};
$js_obj->{nonceStr} = $pay_info->{nonceStr};
$js_obj->{package} = $pay_info->{package}; #提交后微信会返回xml,其中包含了prepay_id:
$js_obj->{signType} = $pay_info->{signType};
$js_obj->{paySign} = $pay_info->{paySign};
$js_obj->{order_info} = $order_info;
write_log("json:".Dumper($js_obj)." $pay_info");
print_html($js_obj);
}
sub print_html {
my $js_obj=$_[0];
my $succ_url = "http://xxxx/pay_success.html"; #支付成功跳转页面
my $fail_url = "http://xxxx/pay_failed.html"; #支付失败跳转页面
print "Content-type:text/html\n\n";
print<<EOF;
<!DOCTYPE html>
<html>
<header>
<title>微信支付</title>
<meta http-equiv=Content-Type content=text/html; charset=utf8>
</header>
<body>
<script type="text/javascript">
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId" : "$js_obj->{appId}", //公众号名称,由商户传入
"timeStamp":"$js_obj->{timeStamp}",//时间戳,自1970年以来的秒数
"nonceStr" :"$js_obj->{nonceStr}",//随机串
"package" : "$js_obj->{package}",
"signType" : "MD5",
"paySign" : "$js_obj->{paySign}" //微信签名
},
function(res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
location.href="$succ_url";
}
else if(res.err_msg == "get_brand_wcpay_request:cancel"){
location.href="$fail_url";
}else{
location.href="$fail_url";
}
}
);
};
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
}else{
onBridgeReady();
}
</script>
</body>
</html>
EOF
exit;
}
6、CGI脚本收到微信的异步支付结果通知,调用服务端接口访问数据库,更新订单支付结果,以及用户的充值金额。
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7
} elsif ($cgi->param("POSTDATA")) {#微信notify_url返回的
my $post_data=$cgi->param("POSTDATA");
my $xml_return = $post_data;
my $json_return = parse_xml_response($xml_return); #本次支付是否成功的xml文档
write_log("xml:".$xml_return."\njson:".Dumper($json_return));
my $pay_result = "success";
if ($json_return->{status} ne "0" || $json_return->{result_code} ne "0") {
$pay_result = "faild"; #支付失败(无订单号信息),直接退出
} elsif ($json_return->{pay_result} ne "0") {
$pay_result = "failed";#支付失败(有订单号信息)
my $ret = update_order_by_id($json_return); #调用服务端接口更新订单支付结果,以及用户的充值金额。
}
print_html_notify_rsp("success");
}
sub print_html_notify_rsp{
my $status=$_[0];
print "Content-type:text/html\n\n";
print $status;
exit;
}