随着互联网和移动互联网发展,越来越多的企业和个人开发者在自己的网站接入支付接口,但是网上从接口级详细介绍的很少,完整成功的案例代码几乎没有。

本文经过对米扑代理接入支付宝接口的实战,成功解决了付款/退款的基本逻辑,并且还解决了自动发货/退货等支付逻辑。

先看成功接入支付宝接口付款/退款,自动发货/退货的网站实例:

米扑代理: 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_goodsis_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)订单状态

alipay-payment-refunds-01

 

b)交易成功订单

alipay-payment-refunds-02

 

c)退款成功订单

alipay-payment-refunds-03

 

参考推荐:

支付宝、财付通、网银、快钱接口的收费费率推荐

支付宝接口付款/退款详细逻辑流程

支付宝php支付接口说明

支付宝在线支付接口申请教程

支付宝服务合同

支付宝即时到账