支付宝接口付款/退款详细逻辑流程
随着互联网和移动互联网发展,越来越多的企业和个人开发者在自己的网站接入支付接口,但是网上从接口级详细介绍的很少,完整成功的案例代码几乎没有。
本文经过对米扑代理接入支付宝接口的实战,成功解决了付款/退款的基本逻辑,并且还解决了自动发货/退货等支付逻辑。
先看成功接入支付宝接口付款/退款,自动发货/退货的网站实例:
米扑代理: 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
侵权处理: 本个人博客,不盈利,若侵犯了您的作品权,请联系博主删除,莫恶意,索钱财,感谢!
转载注明: 支付宝接口付款/退款详细逻辑流程 (米扑博客)