支付宝接口付款/退款详细逻辑流程
随着互联网和移动互联网发展,越来越多的企业和个人开发者在自己的网站接入支付接口,但是网上从接口级详细介绍的很少,完整成功的案例代码几乎没有。
本文经过对米扑代理接入支付宝接口的实战,成功解决了付款/退款的基本逻辑,并且还解决了自动发货/退货等支付逻辑。
先看成功接入支付宝接口付款/退款,自动发货/退货的网站实例:
米扑代理: http://proxy.mimvp.com/price.php
1. 支付宝担保支付的付款/退款状态
// 支付宝交易状态 (user_order)
$ALIPAY_TRADE_STATUS_ARRAY = array( "0"=>"NO_PAY",
"1"=>"WAIT_BUYER_PAY",
"2"=>"WAIT_SELLER_SEND_GOODS",
"3"=>"WAIT_BUYER_CONFIRM_GOODS",
"4"=>"TRADE_FINISHED",
"5"=>"TRADE_CLOSED",
"WAIT_BUYER_PAY"=>"1",
"WAIT_SELLER_SEND_GOODS"=>"2",
"WAIT_BUYER_CONFIRM_GOODS"=>"3",
"TRADE_FINISHED"=>"4",
"TRADE_CLOSED"=>"5",
"0_desc"=>"买家未付款",
"1_desc"=>"等待买家付款",
"2_desc"=>"买家已付款,等待卖家发货",
"3_desc"=>"卖家已发货,等待买家确认",
"4_desc"=>"交易成功结束",
"5_desc"=>"交易中途关闭(已结束,未成功完成)");
// 支付宝退款状态
$ALIPAY_REFUND_STATUS_ARRAY = array("0"=>"NO_REFUND",
"1"=>"WAIT_SELLER_AGREE",
"2"=>"SELLER_REFUSE_BUYER",
"3"=>"WAIT_BUYER_RETURN_GOODS",
"4"=>"WAIT_SELLER_CONFIRM_GOODS",
"5"=>"REFUND_SUCCESS",
"6"=>"REFUND_CLOSED",
"WAIT_SELLER_AGREE"=>"1",
"SELLER_REFUSE_BUYER"=>"2",
"WAIT_BUYER_RETURN_GOODS"=>"3",
"WAIT_SELLER_CONFIRM_GOODS"=>"4",
"REFUND_SUCCESS"=>"5",
"REFUND_CLOSED"=>"6",
"0_desc"=>"未申请退款",
"1_desc"=>"退款协议等待卖家确认中",
"2_desc"=>"卖家不同意协议,等待买家修改",
"3_desc"=>"退款协议达成,等待买家退货",
"4_desc"=>"等待卖家收货",
"5_desc"=>"退款成功",
"6_desc"=>"退款关闭");
以上支付宝的交易状态/退款状态,请见支付宝的担保支付文档,标准双接口(trade_create_by_buyer).pdf
2. 支付宝接口的付款/退款逻辑
//计算得出通知验证结果
$alipayNotify = new AlipayNotify($alipay_config);
$verify_result = $alipayNotify->verifyNotify();
// 验证成功
if($verify_result) {
// 请在这里加上你的业务逻辑程序代码
// 获取支付宝的通知返回参数,可参考技术文档中页面跳转同步通知参数列表
// -------- 支付宝异步通知会多次通知,很多时候因为不同步,例如等待卖家发货自动发货后无法直接输出“seccess”,导致异步通知多次写入状态
// -------- 因此,对user_order表新增一个字段 is_send_goods, 标识是否卖家已发货, 0 - 未发货; 1 - 已发货; 2 - 发货后退款中
$out_trade_no = $_POST['out_trade_no']; // 商户订单号
$trade_no = $_POST['trade_no']; // 支付宝交易号
$trade_status = $_POST['trade_status']; // 交易状态
$trade_status_id = $ALIPAY_TRADE_STATUS_ARRAY[$trade_status];
$seller_email = $_POST['seller_email'];
$seller_id = $_POST['seller_id'];
$seller_actions = $_POST['seller_actions'];
$buyer_email = $_POST['buyer_email'];
$buyer_id = $_POST['buyer_id'];
$buyer_actions = $_POST['buyer_actions'];
$receive_name = $_POST['receive_name'];
$receive_address = $_POST['receive_address'];
$receive_zip = $_POST['receive_zip'];
$receive_phone = $_POST['receive_phone'];
$receive_mobile = $_POST['receive_mobile'];
$logistics_type = $_POST['logistics_type'];
$notify_dtime = $_POST['notify_time'];
$refund_status = $_POST['refund_status'];
$refund_status_id = $ALIPAY_REFUND_STATUS_ARRAY[$refund_status];
$refund_dtime = $_POST['gmt_refund']; // 交易退款时间
$paid_dtime = $_POST['gmt_payment']; // 该笔交易的买家付款时间
// ================= 付款流程 =================
if( $trade_status == 'WAIT_BUYER_PAY' ){
// 等待买家付款
$order_status = 0;
$sql = sprintf( "update user_order
set trade_no='%s', status=%d, trade_status=%d, refund_status=%d, refund_dtime='%s', notify_dtime='%s',
seller_email='%s', seller_id='%s', seller_actions='%s', buyer_email='%s', buyer_id='%s', buyer_actions='%s',
where is_send_goods = 0 and id = '%s'; ",
$trade_no, $order_status, $trade_status_id, $refund_status_id, $refund_dtime, $notify_dtime,
$seller_email, $seller_id, $seller_actions, $buyer_email, $buyer_id, $buyer_actions,
$out_trade_no );
$result = sql_insert( 'mimvp_proxy', $sql );
if ( $result == 1 )
{
echo "success"; // 成功插入数据库,返回通知支付宝不要再发送异步通知
}
}
else if( $trade_status == 'WAIT_SELLER_SEND_GOODS' && $refund_status == "" ) {
// 买家已付款,等待卖家发货,自动填写发货信息
$order_status = 1;
$sql = sprintf( "update user_order
set trade_no='%s', status=%d, trade_status=%d, refund_status=%d, refund_dtime='%s', notify_dtime='%s',
seller_email='%s', seller_id='%s', seller_actions='%s', buyer_email='%s', buyer_id='%s', buyer_actions='%s',
receive_name='%s', receive_address='%s', receive_zip='%s', receive_phone='%s', receive_mobile='%s', paid_dtime='%s'
where is_send_goods = 0 and id = '%s'; ",
$trade_no, $order_status, $trade_status_id, $refund_status_id, $refund_dtime, $notify_dtime,
$seller_email, $seller_id, $seller_actions, $buyer_email, $buyer_id, $buyer_actions,
$receive_name, $receive_address, $receive_zip, $receive_phone, $receive_mobile, $paid_dtime,
$out_trade_no );
$result = sql_insert( 'mimvp_proxy', $sql );
if ( $result == 1 )
{
echo "success"; // 成功插入数据库,返回通知支付宝不要再发送异步通知
}
// // 卖家发货,发货后状态会更新为等待买家确认收货
// $send_goods_success_status = is_send_goods_success($trade_no);
// if( $send_goods_success_status == 0 ) {
// is_send_goods_success($trade_no);
// }
// 此状态不返回“success”,让支付宝多次回调此函数,自动发货(在发货接口返回“success”)
$sHtml = "<form id='confirm_submit' name='confirm_submit' action='apiconfirm.php' method='POST' target='_blank'>";
$sHtml = $sHtml . "<input type='hidden' name='WIDtrade_no' value='" . $trade_no . "' />";
$sHtml = $sHtml . "<input type='hidden' name='WIDlogistics_name' value='无需物流' />";
$sHtml = $sHtml . "<input type='hidden' name='WIDinvoice_no' value='0' />";
$sHtml = $sHtml . "<input type='hidden' name='WIDtransport_type' value='EXPRESS' />";
$sHtml = $sHtml . "<script>document.forms['confirm_submit'].submit();setTimeout(function(){window.close();}, 1000);</script>";
echo $sHtml;
// $confirm_received_url = "../usercenter/orderdetail.php?order_id=" . $out_trade_no;
// header("Location: " . $confirm_received_url);
}
else if( $trade_status == 'WAIT_BUYER_CONFIRM_GOODS' && $refund_status == "" ){
// 卖家已发货, 等待买家确认收货,跳转到用户支付页面
$order_status = 2;
$sql = sprintf( "update user_order set status=%d, trade_status=%d, seller_actions='%s', buyer_actions='%s', notify_dtime='%s' where is_send_goods = 1 and is_finish = 0 and id = '%s'; ",
$order_status, $trade_status_id, $seller_actions, $buyer_actions, $notify_dtime, $out_trade_no );
$result = sql_insert( 'mimvp_proxy', $sql );
if ( $result == 1 )
{
echo "success"; // 成功插入数据库,返回通知支付宝不要再发送异步通知
}
// $confirm_received_url = 'https://lab.alipay.com/consume/queryTradeDetail.htm?actionName=CONFIRM_GOODS&tradeNo=' . $trade_no;;
// header("Location: " . $confirm_received_url);
}
else if( $trade_status == 'TRADE_FINISHED' && $refund_status == "" ) {
// 用户支付完成,交易成功,跳转到订单详情页
$order_status = 4;
$order_confirm_dtime = date('Y-m-d H:i:s');
$order_finish_dtime = date('Y-m-d H:i:s');
$sql = sprintf( "update user_order set status=%d, trade_status=%d, seller_actions='%s', buyer_actions='%s', confirm_dtime='%s', finish_dtime='%s', notify_dtime='%s', is_finish = 1 where is_send_goods = 1 and id = '%s'; ",
$order_status, $trade_status_id, $seller_actions, $buyer_actions, $order_confirm_dtime, $order_finish_dtime, $notify_dtime, $out_trade_no );
$result = sql_insert( 'mimvp_proxy', $sql );
if ( $result == 1 )
{
echo "success"; // 成功插入数据库,返回通知支付宝不要再发送异步通知
}
// $orderdetail_url = "../usercenter/orderdetail.php?order_id=" . $out_trade_no;
// header("Location: " . $orderdetail_url);
}
else if( $trade_status == 'TRADE_CLOSED' && $refund_status == "" ){
// 交易中途关闭(已结束,未成功完成)
$order_status = 32;
$order_finish_dtime = date('Y-m-d H:i:s');
$sql = sprintf( "update user_order set status=%d, trade_status=%d, seller_actions='%s', buyer_actions='%s', finish_dtime='%s', notify_dtime='%s', is_finish = 1 where is_send_goods = 1 and id = '%s'; ",
$order_status, $trade_status_id, $seller_actions, $buyer_actions, $order_finish_dtime, $notify_dtime, $out_trade_no );
$result = sql_insert( 'mimvp_proxy', $sql );
if ( $result == 1 )
{
echo "success"; // 成功插入数据库,返回通知支付宝不要再发送异步通知
}
// $orderdetail_url = "../usercenter/orderdetail.php?order_id=" . $out_trade_no;
// header("Location: " . $orderdetail_url);
}
// ================= 退款流程 =================
if( $refund_status == 'WAIT_SELLER_AGREE' && $trade_status == 'WAIT_SELLER_SEND_GOODS' ) {
# 退款协议等待卖家确认中, 买家申请退款,进入退款流程(买家已经付款,等待卖家发货)
$order_status = 101;
$refund_create_dtime = date('Y-m-d H:i:s');
$refund_reason = '';
$sql = sprintf( "update user_order set status=%d, trade_status=%d, refund_status=%d, refund_create_dtime='%s', refund_reason='%s', seller_actions='%s', buyer_actions='%s', notify_dtime='%s' where id = '%s'; ",
$order_status, $trade_status_id, $refund_status_id, $refund_create_dtime, $refund_reason, $seller_actions, $buyer_actions, $notify_dtime, $out_trade_no );
$result = sql_insert( 'mimvp_proxy', $sql );
if ( $result == 1 )
{
echo "success"; // 成功插入数据库,返回通知支付宝不要再发送异步通知
}
}
else if( $refund_status == 'WAIT_SELLER_AGREE' && $trade_status == 'WAIT_BUYER_CONFIRM_GOODS' ) {
# 退款协议等待卖家确认中, 买家申请退款,进入退款流程(卖家已发货,等待买家确认收货)
$order_status = 101;
$refund_create_dtime = date('Y-m-d H:i:s');
$refund_reason = '';
$sql = sprintf( "update user_order set status=%d, trade_status=%d, refund_status=%d, refund_create_dtime='%s', refund_reason='%s', seller_actions='%s', buyer_actions='%s', notify_dtime='%s', is_send_goods = 2 where id = '%s'; ",
$order_status, $trade_status_id, $refund_status_id, $refund_create_dtime, $refund_reason, $seller_actions, $buyer_actions, $notify_dtime, $out_trade_no );
$result = sql_insert( 'mimvp_proxy', $sql );
if ( $result == 1 )
{
echo "success"; // 成功插入数据库,返回通知支付宝不要再发送异步通知
}
}
else if( $refund_status == 'SELLER_REFUSE_BUYER' && $trade_status == 'WAIT_SELLER_SEND_GOODS' ) {
# 卖家不同意退款,等待买家修改, 此时买家可再申请退款也可继续走正常的交易流程(买家已付款,等待卖家发货)
$order_status = 102;
$refund_refuse_dtime = date('Y-m-d H:i:s');
$refund_refuse_reason = '';
$sql = sprintf( "update user_order set status=%d, trade_status=%d, refund_status=%d, refund_refuse_dtime='%s', refund_refuse_reason='%s', seller_actions='%s', buyer_actions='%s', notify_dtime='%s' where id = '%s'; ",
$order_status, $trade_status_id, $refund_status_id, $refund_refuse_dtime, $refund_refuse_reason, $seller_actions, $buyer_actions, $notify_dtime, $out_trade_no );
$result = sql_insert( 'mimvp_proxy', $sql );
if ( $result == 1 )
{
echo "success"; // 成功插入数据库,返回通知支付宝不要再发送异步通知
}
}
else if( $refund_status == 'SELLER_REFUSE_BUYER' && $trade_status == 'WAIT_BUYER_CONFIRM_GOODS' ) {
# 卖家不同意退款,等待买家修改, 此时买家可再申请退款也可继续走正常的交易流程(买家已付款,等待买家确认收货)
$order_status = 102;
$refund_refuse_dtime = date('Y-m-d H:i:s');
$refund_refuse_reason = '';
$sql = sprintf( "update user_order set status=%d, trade_status=%d, refund_status=%d, refund_refuse_dtime='%s', refund_refuse_reason='%s', seller_actions='%s', buyer_actions='%s', notify_dtime='%s' where id = '%s'; ",
$order_status, $trade_status_id, $refund_status_id, $refund_refuse_dtime, $refund_refuse_reason, $seller_actions, $buyer_actions, $notify_dtime, $out_trade_no );
$result = sql_insert( 'mimvp_proxy', $sql );
if ( $result == 1 )
{
echo "success"; // 成功插入数据库,返回通知支付宝不要再发送异步通知
}
}
else if( $refund_status == 'WAIT_BUYER_RETURN_GOODS' && $trade_status == 'WAIT_SELLER_SEND_GOODS' ) {
# 退款协议达成,等待买家退货(买家已付款,等待卖家发货)
# 此状态(卖家未发货,等待买家退货),一般不会发生
# --------- 此处需要添加修改退款逻辑,即修改订单,用户信息中的有效时间(因为没有发货,所以无需修改订单) ---------
# --------- 因为没有发货,所以【把没有发货的时间戳截图发给卖家】,等卖家看到没有发货时间戳后,即可在支付宝界面确认退款
# --------- 其实,此处没必要【把没有发货的时间戳截图发给卖家】,因为卖家在支付宝交易号上看到没有【发货时间】(没有调用支付宝卖家发货接口)
$order_status = 103;
$refund_return_dtime = date('Y-m-d H:i:s');
$sql = sprintf( "update user_order set status=%d, trade_status=%d, refund_status=%d, refund_return_dtime='%s', seller_actions='%s', buyer_actions='%s', notify_dtime='%s' where id = '%s'; ",
$order_status, $trade_status_id, $refund_status_id, $refund_return_dtime, $seller_actions, $buyer_actions, $notify_dtime, $out_trade_no );
$result = sql_insert( 'mimvp_proxy', $sql );
if ( $result == 1 )
{
echo "success"; // 成功插入数据库,返回通知支付宝不要再发送异步通知
}
}
else if( $refund_status == 'WAIT_BUYER_RETURN_GOODS' && $trade_status == 'WAIT_BUYER_CONFIRM_GOODS' ) {
# 退款协议达成,等待买家退货(卖家已发货,等待买家确认收货)
# --------- 此处需要添加修改退款逻辑,即修改订单,用户信息中的有效时间 ---------
# --------- 当修改订单撤销有效时间后,会更新退货时间(refund_return_dtime),并【把退货时间戳截图发给卖家】,
# --------- 等卖家看到有退货日期后,即可在支付宝界面确认退款
// $order_status = 103;
// $refund_return_dtime = date('Y-m-d H:i:s');
// $sql = sprintf( "update user_order set status=%d, refund_status=%d, refund_return_dtime='%s', seller_actions='%s', buyer_actions='%s', notify_dtime='%s' where id = '%s'; ",
// $order_status, $refund_status_id, $refund_return_dtime, $seller_actions, $buyer_actions, $notify_dtime, $out_trade_no );
// $result = sql_insert( 'mimvp_proxy', $sql );
// if ( $result == 1 )
// {
// echo "success"; // 成功插入数据库,返回通知支付宝不要再发送异步通知
// }
$refund_return_success_status = is_refund_return_success($out_trade_no, $buyer_actions, $seller_actions, $trade_status_id, $refund_status_id);
if( $refund_return_success_status == 1) {
echo "success"; // 成功插入数据库,返回通知支付宝不要再发送异步通知
}
}
else if( $refund_status == 'WAIT_SELLER_CONFIRM_GOODS' && $trade_status == 'WAIT_SELLER_SEND_GOODS' ) {
# 等待卖家收货(买家已付款,等待卖家发货)
$order_status = 104;
$sql = sprintf( "update user_order set status=%d, trade_status=%d, refund_status=%d, seller_actions='%s', buyer_actions='%s', notify_dtime='%s' where id = '%s'; ",
$order_status, $trade_status_id, $refund_status_id, $seller_actions, $buyer_actions, $notify_dtime, $out_trade_no );
$result = sql_insert( 'mimvp_proxy', $sql );
if ( $result == 1 )
{
echo "success"; // 成功插入数据库,返回通知支付宝不要再发送异步通知
}
}
else if( $refund_status == 'WAIT_SELLER_CONFIRM_GOODS' && $trade_status == 'WAIT_BUYER_CONFIRM_GOODS' ) {
# 等待卖家收货(卖家已发货,等待买家收货)
$order_status = 104;
$sql = sprintf( "update user_order set status=%d, trade_status=%d, refund_status=%d, seller_actions='%s', buyer_actions='%s', notify_dtime='%s' where id = '%s'; ",
$order_status, $trade_status_id, $refund_status_id, $seller_actions, $buyer_actions, $notify_dtime, $out_trade_no );
$result = sql_insert( 'mimvp_proxy', $sql );
if ( $result == 1 )
{
echo "success"; // 成功插入数据库,返回通知支付宝不要再发送异步通知
}
}
else if( $refund_status == 'REFUND_SUCCESS' /* && $trade_status == 'TRADE_CLOSED' */ ) {
# 退款成功, 交易中途关闭(已结束,未成功完成)
$order_status = 105;
$refund_finish_dtime = date('Y-m-d H:i:s');
$sql = sprintf( "update user_order set status=%d, trade_status=%d, refund_status=%d, refund_dtime='%s', refund_finish_dtime='%s', seller_actions='%s', buyer_actions='%s', notify_dtime='%s', is_finish = 1 where id = '%s'; ",
$order_status, $trade_status_id, $refund_status_id, $refund_dtime, $refund_finish_dtime, $seller_actions, $buyer_actions, $notify_dtime, $out_trade_no );
$result = sql_insert( 'mimvp_proxy', $sql );
if ( $result == 1 )
{
echo "success"; // 成功插入数据库,返回通知支付宝不要再发送异步通知
}
}
else if( $refund_status == 'REFUND_CLOSED' /* && $trade_status == 'TRADE_FINISHED' */ ) {
# 退款关闭, 买卖双方终止了退款操作,并走正常交易流程完成了交易(卖家拒绝退款,买家确认收货,交易完成)
$order_status = 106;
$refund_finish_dtime = date('Y-m-d H:i:s');
$sql = sprintf( "update user_order set status=%d, trade_status=%d, refund_status=%d, refund_finish_dtime='%s', seller_actions='%s', buyer_actions='%s', notify_dtime='%s', is_finish = 1 where id = '%s'; ",
$order_status, $trade_status_id, $refund_status_id, $refund_finish_dtime, $seller_actions, $buyer_actions, $notify_dtime, $out_trade_no );
$result = sql_insert( 'mimvp_proxy', $sql );
if ( $result == 1 )
{
echo "success"; // 成功插入数据库,返回通知支付宝不要再发送异步通知
}
}
}
else {
//验证失败
//如要调试,请看alipay_notify.php页面的verifyReturn函数
echo "验证失败" . "<br>";
echo "verify_result : " . $verify_result . "<br>";
}
以上代码,付款后回调的同步/异步代码,也是最核心关键的代码(干货),里面设置了两个附加状态标识,以及自动发货的调用代码,下文将逐步做相应介绍。
3. 自动发货
支付宝担保交易实例中,给了一个发货实例:
一个php页面,填写物流信息后,点击提交按钮,POST给发货的逻辑处理函数。这个过程都是手动操作的,可以通过javascript改造模拟成自动提交发货,自动发货代码即在步骤2)中:
else if( $trade_status == 'WAIT_SELLER_SEND_GOODS' && $refund_status == "" ) {
// 买家已付款,等待卖家发货,自动填写发货信息
$order_status = 1;
$sql = sprintf( "update user_order
set trade_no='%s', status=%d, trade_status=%d, refund_status=%d, refund_dtime='%s', notify_dtime='%s',
seller_email='%s', seller_id='%s', seller_actions='%s', buyer_email='%s', buyer_id='%s', buyer_actions='%s',
receive_name='%s', receive_address='%s', receive_zip='%s', receive_phone='%s', receive_mobile='%s', paid_dtime='%s'
where is_send_goods = 0 and id = '%s'; ",
$trade_no, $order_status, $trade_status_id, $refund_status_id, $refund_dtime, $notify_dtime,
$seller_email, $seller_id, $seller_actions, $buyer_email, $buyer_id, $buyer_actions,
$receive_name, $receive_address, $receive_zip, $receive_phone, $receive_mobile, $paid_dtime,
$out_trade_no );
$result = sql_insert( 'mimvp_proxy', $sql );
if ( $result == 1 )
{
echo "success"; // 成功插入数据库,返回通知支付宝不要再发送异步通知
}
// 此状态不返回“success”,让支付宝多次回调此函数,自动发货(在发货接口返回“success”)
$sHtml = "<form id='confirm_submit' name='confirm_submit' action='apiconfirm.php' method='POST' target='_blank'>";
$sHtml = $sHtml . "<input type='hidden' name='WIDtrade_no' value='" . $trade_no . "' />";
$sHtml = $sHtml . "<input type='hidden' name='WIDlogistics_name' value='无需物流' />";
$sHtml = $sHtml . "<input type='hidden' name='WIDinvoice_no' value='0' />";
$sHtml = $sHtml . "<input type='hidden' name='WIDtransport_type' value='EXPRESS' />";
$sHtml = $sHtml . "<script>document.forms['confirm_submit'].submit();setTimeout(function(){window.close();}, 1000);</script>";
echo $sHtml;
}
上面模拟自动发货,当然很好,但也导致了一个非常严重的问题:调用自动发货代码,一定要打印输出 echo $sHtml ,这将导致无法只成功输出“success”通知支付宝不要再发异步通知了,这是因为支付宝官方文档:程序执行完后必须打印输出“success”(不包含引号)。如果商户反馈给支付宝的字符不是 success 这 7 个字符,支付宝服务器会不断重发通知,直到
超过 24 小时 22 分钟。一般情况下,25 小时以内完成 8 次通知(通知的间隔频率一般是:2m,10m,10m,1h,2h,6h,15h)
因此,我增加了两个附加的状态标识: is_send_goods 和 is_finish,分别标识是否发货成功,是否交易成功/关闭,目的就是为解决自动发货无法输出“success”剔除支付宝重复异步通知。
剔除支付宝重复异步通知的方法是在数据库插入的where条件语句做了个技巧: where is_send_goods = 0 and ... " 因为自动发货成功后,我会在发货接口(action='apiconfirm.php')中更新 is_send_goods = 1,并且支付宝收货接口("service" => "send_goods_confirm_by_platform")得到发货后,会更新异步通知状态为 $trade_status == 'WAIT_BUYER_CONFIRM_GOODS'
发货接口(action='apiconfirm.php')代码如下:
$trade_no = $_POST['WIDtrade_no']; // 支付宝交易号 (必填)
$logistics_name = $_POST['WIDlogistics_name']; // 物流公司名称 (必填)
$invoice_no = $_POST['WIDinvoice_no']; // 物流发货单号
$transport_type = $_POST['WIDtransport_type']; // 物流运输类型 (三个值可选:POST(平邮)、EXPRESS(快递)、EMS(EMS))
//构造要请求的参数数组,无需改动
$parameter = array(
"service" => "send_goods_confirm_by_platform",
"partner" => trim($alipay_config['partner']),
"trade_no" => $trade_no,
"logistics_name" => $logistics_name,
"invoice_no" => $invoice_no,
"transport_type" => $transport_type,
"_input_charset" => trim(strtolower($alipay_config['input_charset']))
);
//建立请求
$alipaySubmit = new AlipaySubmit($alipay_config);
$html_text = $alipaySubmit->buildRequestHttp($parameter);
// $html_text = $alipaySubmit->buildRequestForm($parameter,"get", "确认");
// echo "</br></br>html_text: " . $html_text . "</br></br>";
//解析XML
//注意:该功能PHP5环境及以上支持,需开通curl、SSL等PHP配置环境。建议本地调试时使用PHP开发软件
$doc = new DOMDocument();
$doc->loadXML($html_text);
//请在这里加上商户的业务逻辑程序代码
//获取支付宝的通知返回参数,可参考技术文档中页面跳转同步通知参数列表
//解析XML
// if( ! empty($doc->getElementsByTagName( "alipay" )->item(0)->nodeValue) ) {
// $alipay = $doc->getElementsByTagName( "alipay" )->item(0)->nodeValue;
// echo $alipay;
// }
$alipay = $doc->getElementsByTagName("alipay")->item(0);
$is_success = $alipay->getElementsByTagName("is_success")->item(0)->nodeValue; // $is_success
$sign = $alipay->getElementsByTagName("sign")->item(0)->nodeValue;
$sign_type = $alipay->getElementsByTagName("sign_type")->item(0)->nodeValue;
$response = $alipay->getElementsByTagName("response")->item(0);
$tradeBase = $response->getElementsByTagName("tradeBase")->item(0);
$out_trade_no = $tradeBase->getElementsByTagName("out_trade_no")->item(0)->nodeValue;
$seller_actions = $tradeBase->getElementsByTagName("seller_actions")->item(0)->nodeValue;
$buyer_actions = $tradeBase->getElementsByTagName("buyer_actions")->item(0)->nodeValue;
$req = $alipay->getElementsByTagName('request')->item(0);
$res = $alipay->getElementsByTagName('response')->item(0);
if ($is_success) {
// 卖家发货成功,记录下发货时间,并写入过期时间(以发货时间为准,加上会员/套餐时间)
// 1. 查询订单号,获取订单的套餐类型(会员/天/周/月/年), 数量,以及用户表里的过期时间,用于重新计算过期时间
// 2. 写入订单的状态,收货时间,上次有效时间,过期时间
// 3. 写入用户的状态,包括过期时间
// 4. 跳转到买家商品详情页,等待买家确认收货
// 1. 查询订单号
$unit_quantity = 0;
$unit_name_en = 'day';
$sql = sprintf("select count(1) as count, unit_quantity, unit_name_en, user_email, is_send_goods from user_order where id = '%s';", $out_trade_no);
$result = sql_select( 'mimvp_proxy', $sql );
$row = $result->fetch();
if( $row['count'] != 1) {
$row = sql_select( 'mimvp_proxy', $sql );
}
$unit_quantity = $row['unit_quantity'];
$unit_name_en = $row['unit_name_en'];
$user_email = $row['user_email'];
$is_send_goods = $row['is_send_goods'];
if($is_send_goods != 0) { # 如果订单状态已经发货,则不执行下面的操作
return;
}
// 1. 查询用户信息
$sql = sprintf("select count(1) as count, proxy_expire_dtime, club_expire_dtime from user_info where user_email = '%s';", $user_email);
$result = sql_select( 'mimvp_proxy', $sql );
$row = $result->fetch();
if( $row['count'] != 1) {
$row = sql_select( 'mimvp_proxy', $sql );
}
$proxy_expire_dtime = $row['proxy_expire_dtime'];
$club_expire_dtime = $row['club_expire_dtime'];
// 过期时间默认为套餐过期时间, 若为会员订单则为会员过期时间
$expire_dtime = $proxy_expire_dtime;
$order_type = ""; // 订单类型
if($unit_name_en == "club") {
$order_type = "club"; // 获取产品类型,会员则先转成年(year),然后写入user_info的club_expire_dtime,否则写入user_info的proxy_expire_dtime
$unit_name_en = "year";
$expire_dtime = $club_expire_dtime;
}
$last_expire_dtime = $expire_dtime; // 上次过期时间,若会员则是会员过期时间;若套餐则是套餐过期时间
$expire_dtime_interval = '+' . $unit_quantity . ' ' . $unit_name_en; // '+1 day'
// 2. 写入订单的状态
$order_status = 2;
$trade_status = 3;
$order_send_dtime = date('Y-m-d H:i:s');
// 计算过期时间时,有两种情况:
// 1) 上次过期时间小于等于当前发货时间,则以当前发货时间作为上次过期时间计算
// 2) 上次过期时间大于当前发货时间,则以上次过期时间为起点计算,加上现在购买的套餐时间
$order_expire_dtime = date('YmdHis', strtotime($expire_dtime_interval, strtotime($order_send_dtime)));
$order_send_dtime_int = date('YmdHis', strtotime($order_send_dtime));
if( $last_expire_dtime > $order_send_dtime_int) // 上次过期时间大于本次发货时间
{
$order_expire_dtime = date('YmdHis', strtotime($expire_dtime_interval, strtotime($last_expire_dtime)));
} else {
// $last_expire_dtime = $order_send_dtime; // 上次过期时间小于本次发货时间,则以本次发货时间计算
}
$is_send_goods = 1; # ---- 此处发货成功,修改完上次,本次有效时间,同时更新已发货成功【重要】
$last_expire_dtime = date('Y-m-d H:i:s', strtotime($expire_dtime)); # 转换整型为日期字符型
// if($expire_dtime == 0) { # 如果上次有效时间为0(第一次购买),则默认为'0000-00-00 00:00:00'
// $last_expire_dtime = '0000-00-00 00:00:00';
// }
$sql = sprintf( "update user_order set status=%d, trade_status=%d, buyer_actions='%s', seller_actions='%s', send_dtime='%s', last_expire_dtime='%s', expire_dtime=%d, is_send_goods = 1 where is_send_goods = 0 and id = '%s'; ",
$order_status, $trade_status, $buyer_actions, $seller_actions, $order_send_dtime, $last_expire_dtime, $order_expire_dtime, $out_trade_no );
$insert_order_success = -1; // -1 failed; 0 - no_change; 1 - success
$result = sql_insert( 'mimvp_proxy', $sql );
if ( $result == 1 )
{
$insert_order_success = 1;
}
// 3. 写入用户信息的状态
$sql = sprintf( "update user_info set proxy_expire_dtime=%d where user_email = '%s'; ", $order_expire_dtime, $user_email );
if( $order_type == "club" ) {
$sql = sprintf( "update user_info set club_expire_dtime=%d where user_email = '%s'; ", $order_expire_dtime, $user_email );
}
$insert_userinfo_success = -1; // -1 failed; 0 - no_change; 1 - success
$result = sql_insert( 'mimvp_proxy', $sql );
if ( $result == 1 )
{
$insert_userinfo_success = 1;
}
// 如果更新订单和用户表都成功,则返回"success", 成功插入数据库,返回通知支付宝不要再发送异步通知
if($insert_order_success == 1 && $insert_userinfo_success == 1) {
echo "success";
}
// 4. 跳转到买家商品详情页,等待买家确认收货
$confirm_received_url = "../usercenter/orderdetail.php?order_id=" . $out_trade_no;
header("Location: " . $confirm_received_url);
}
4. 自动退货
自动退货条件和代码,请见步骤2)中的
else if( $refund_status == 'WAIT_BUYER_RETURN_GOODS' && $trade_status == 'WAIT_BUYER_CONFIRM_GOODS' ) {
# 退款协议达成,等待买家退货(卖家已发货,等待买家确认收货)
# --------- 此处需要添加修改退款逻辑,即修改订单,用户信息中的有效时间 ---------
# --------- 当修改订单撤销有效时间后,会更新退货时间(refund_return_dtime),并【把退货时间戳截图发给卖家】,
# --------- 等卖家看到有退货日期后,即可在支付宝界面确认退款
$refund_return_success_status = is_refund_return_success($out_trade_no, $buyer_actions, $seller_actions, $trade_status_id, $refund_status_id);
if( $refund_return_success_status == 1) {
echo "success"; // 成功插入数据库,返回通知支付宝不要再发送异步通知
}
}
模拟用户自动退货的代码(is_refund_return_success(...))如下:
/**
* 功能: 判断退货是否成功,退货撤销订单有效时间及状态
* @param unknown $out_trade_no
* @param unknown $buyer_actions
* @param unknown $seller_actions
* @return number
*/
function is_refund_return_success($out_trade_no, $buyer_actions, $seller_actions, $trade_status_id, $refund_status_id)
{
// 买家要求退货,记录下退货时间,撤销有效时间并写入过期时间(以发货时间为准,减去会员/套餐时间)
// 1. 查询订单号,获取订单的套餐类型(会员/天/周/月/年), 数量,以及上次过期时间,过期时间,用于撤销重新计算过期时间
// 2. 写入订单的状态,收货时间,上次有效时间,过期时间
// 3. 写入用户的状态,包括过期时间
// 4. 截图退货时间(refund_return_dtime),发给卖家,卖家看到退货时间后确认退货
$refund_return_success_status = 0; // 0 - 退款更新失败; 1 - 退款更新成功
// 1. 查询订单号
$sql = sprintf("select count(1) as count, unit_quantity, unit_name_en, user_email, last_expire_dtime, expire_dtime from user_order where id = '%s';", $out_trade_no);
$result = sql_select( 'mimvp_proxy', $sql );
$row = $result->fetch();
if( $row['count'] != 1) {
$row = sql_select( 'mimvp_proxy', $sql );
}
$unit_quantity = $row['unit_quantity'];
$unit_name_en = $row['unit_name_en']; // 订单类型(club; day/week/month/year)
$user_email = $row['user_email'];
$last_expire_dtime = $row['last_expire_dtime'];
$expire_dtime = $row['expire_dtime'];
// 2. 写入订单的状态
$order_status = 103; # 101 - 申请退款中; 102 - 卖家不同意退款; 103 - 等待买家退货; 104 - 卖家收货; 105 - 退款成功; 106 - 退款关闭;
$refund_status = 3; # 3 - WAIT_BUYER_RETURN_GOODS
$refund_return_dtime = date('Y-m-d H:i:s');
# 退款订单,撤销后正好对掉上次有效时间($expire_dtime)和有效时间($last_expire_dtime)
$expire_dtime = date('Y-m-d H:i:s', strtotime($expire_dtime));
$last_expire_dtime = date('YmdHis', strtotime($last_expire_dtime));
$sql = sprintf( "update user_order set status=%d, trade_status=%d, refund_status=%d, buyer_actions='%s', seller_actions='%s', refund_return_dtime='%s', last_expire_dtime='%s', expire_dtime=%d where id = '%s'; ",
$order_status, $trade_status_id, $refund_status_id, $buyer_actions, $seller_actions, $refund_return_dtime, $expire_dtime, $last_expire_dtime, $out_trade_no );
$insert_order_success = -1; // -1 failed; 0 - no_change; 1 - success
$result = sql_insert( 'mimvp_proxy', $sql );
if ( $result == 1 )
{
$insert_order_success = 1;
}
// 3. 写入用户信息的状态
$sql = sprintf( "update user_info set proxy_expire_dtime=%d where user_email = '%s'; ", $last_expire_dtime, $user_email );
if( $unit_name_en == "club" ) {
$sql = sprintf( "update user_info set club_expire_dtime=%d where user_email = '%s'; ", $last_expire_dtime, $user_email );
}
$insert_userinfo_success = -1; // -1 failed; 0 - no_change; 1 - success
$result = sql_insert( 'mimvp_proxy', $sql );
if ( $result == 1 )
{
$insert_userinfo_success = 1;
}
// 如果更新订单和用户表都成功,则返回"success"
if($insert_order_success == 1 && $insert_userinfo_success == 1) {
$refund_return_success_status = 1;
}
return $refund_return_success_status;
}
5. 附加状态标识
我增加了两个附加的状态标识: is_send_goods 和 is_finish,分别标识是否发货成功,是否交易成功/关闭,目的就是为解决自动发货无法输出“success”剔除支付宝重复异步通知。
两个状态标识,都存储在数据库中的订单记录里,自动发货需要这两个状态标识就足够了,如果大家需要更多的判断和状态,可以根据自己的需求增加一些状态标识(建议越少越好!)
6. 常见逻辑错误分析
在网上看到过一篇文章《做过支付宝接口退款的请进》,他把交易状态和退款状态,全部用 if ... else if ... else if ... else 连接判断,其实这逻辑是错误的,也是他遇到问题的核心原因所在!
在此简单模拟他的if ... else if 逻辑,模拟示例代码:
$a = 1;
$b = 2;
if($a == 1) {
echo "111 <br>";
}
else if($a == 1 && $b == 2){
echo "222 <br>";
}
大家猜输出结果是多少? 输出 “111",还是输出“111"和”222"?
大家可以自己去试下,这里为了方便分析他的问题,给出机器给出的答案,只会输出“111"
OK,现在回过头去看下他的问题,原因一目了然吧,哈哈
else if (Request.Form["trade_status"] == "WAIT_SELLER_SEND_GOODS")
后,再在后面调用判断
else if (Request.Form["refund_status"] == "SELLER_REFUSE_BUYER" && Request.Form["trade_status"] == "WAIT_SELLER_SEND_GOODS")
else if (Request.Form["refund_status"] == "WAIT_BUYER_RETURN_GOODS" && Request.Form["trade_status"] == "WAIT_SELLER_SEND_GOODS")
else if (Request.Form["refund_status"] == "WAIT_SELLER_CONFIRM_GOODS" && Request.Form["trade_status"] == "WAIT_SELLER_SEND_GOODS")
三条语句,都是不会被走到执行的!
解决的办法,就是按照我在步骤2)中的方法,对付款交易状态和退款状态,分开用if进行判断。
7. 自定义付款/退款状态码
合并支付宝交易状态,退款状态后,我自定义了自己的状态标识码,方便统一显示和操作,自定义付款/退款状态码如下:
$status_array = array("0"=>"已下单", "1"=>"已付款", "2"=>"已收货", "4"=>"已到帐", "32"=>"逾期未付", "64"=>"已过期", "101"=>"申请退款中", "102"=>"拒绝退款", "103"=>"买家退货中", "104"=>"已退货", "105"=>"退款成功", "106"=>"退款关闭");
$status_op_array = array("0"=>"付款", "1"=>"请求发货", "2"=>"确认付款", "4"=>"交易成功", "32"=>"逾期未付", "64"=>"续费", "101"=>"卖家确认中", "102"=>"修改退款", "103"=>"确认已退货", "104"=>"卖家收货中", "105"=>"退款成功", "106"=>"交易成功");
8. 成功案例
本文是我成功接入支付宝到米扑代理后,并实现了自动发货,自动退货的过程中,遇到的问题,解决的办法,希望对大家有帮助。
米扑代理成功案例:
http://proxy.mimvp.com/price.php
a)订单状态
b)交易成功订单
c)退款成功订单
参考推荐:
支付宝、财付通、网银、快钱接口的收费费率 (推荐)
版权所有: 本文系米扑博客原创、转载、摘录,或修订后发表,最后更新于 2016-11-29 00:14:48
侵权处理: 本个人博客,不盈利,若侵犯了您的作品权,请联系博主删除,莫恶意,索钱财,感谢!
转载注明: 支付宝接口付款/退款详细逻辑流程 (米扑博客)


