On this page
拒绝or同意退款
控制器 app/controller/admin/Order.php
php
// 拒绝/同意 申请退款
public function handleRefund(){
// 获取当前订单
$order = request()->Model;
// 判断订单状态是否正确
if ($order->refund_status !== 'applied') {
ApiException('订单状态不正确');
}
$param = request()->param();
// 是否同意退款
if ($param['agree']) {
// 同意
$this->__refundOrder($order);
} else {
// 拒绝退款
$order->extra = [
'refund_reason'=> isset($order->extra->refund_reason) ? $order->extra->refund_reason: null,
'refund_disagree_reason'=>$param['disagree_reason']
];
$order->refund_status = 'pending';
$order->save();
}
return showSuccess($order);
}
// 退款逻辑
public function __refundOrder($order){
switch ($order->payment_method) {
case 'alipay':
// 生成退款单号
$refundNo = \app\model\Order::setRefundOrderNo();
// 调用支付宝退款
$obj = new \app\controller\common\AliPay();
$alipay = $obj->alipay;
$res = $alipay->refund([
'out_trade_no' => $order->no, // 之前的订单流水号
'refund_amount' => $order->total_price, // 退款金额,单位元
'out_request_no' => $refundNo, // 退款订单号
]);
// 根据支付宝的文档,如果返回值里有 sub_code 字段说明退款失败
if ($res->sub_code) {
// 将订单的退款状态标记为退款失败
$order->refund_no = $refundNo;
$order->refund_status ='failed';
$order->extra =[
'refund_failed_code'=>$res->sub_code
];
$order->save();
} else {
// 将订单的退款状态标记为退款成功并保存退款订单号
$order->refund_no = $refundNo;
$order->refund_status ='success';
$order->save();
}
break;
case 'wechat':
// 生成退款单号
$refundNo = \app\model\Order::setRefundOrderNo();
// 调用微信退款
$obj = new \app\controller\common\wechatPay();
$wechat = $obj->wechat;
$res = $wechat->refund([
'type' => 'app',
'out_trade_no' => $order->no, // 之前的订单流水号
'out_refund_no' => $refundNo, // 退款订单号
'total_fee' => strval($order->total_price*100), // 退款金额,单位元
'refund_fee' => strval($order->total_price*100), // 退款金额,单位元
'refund_desc' => $order->extra->refund_reason,
]);
if ($res->return_code !== 'SUCCESS') {
// 将订单的退款状态标记为退款失败
$order->refund_no = $refundNo;
$order->refund_status ='failed';
$order->extra =[
'refund_failed_code'=>$res->return_msg
];
$order->save();
} else {
// 将订单的退款状态标记为退款成功并保存退款订单号
$order->refund_no = $refundNo;
$order->refund_status ='success';
$order->save();
}
break;
}
}
模型 app/model/Order.php
php
// 生成退款单号
public static function setRefundOrderNo(){
do {
$prefix = 'refund'.date('YmdHis');
// 随机生成 6 位的数字
$no = $prefix.str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
} while (self::where('refund_no',$no)->find());
return $no;
}
安装支付扩展包 文档地址:https://pay.yanda.net.cn/docs/2.x/overview
php
composer require yansongda/pay
支付宝支付 app/controller/common/AliPay.php
php
<?php
namespace app\controller\common;
use think\Request;
use Yansongda\Pay\Pay;
class AliPay
{
public $alipay;
// 初始化
public function __construct(){
$config = config('cms.payment.alipay');
$this->alipay = Pay::alipay(config('cms.payment.alipay'));
}
// 网页支付
public function web($order){
return $this->alipay->web($order)->send();
}
// app支付
public function app($order){
return $this->alipay->app($order)->send();
}
}
微信支付 app/controller/common/wechatPay.php
php
<?php
namespace app\controller\common;
use think\Request;
use Yansongda\Pay\Pay;
class wechatPay
{
public $wechat;
public function __construct(){
$config = config('cms.payment.wechat');
$this->wechat = Pay::wechat($config);
}
// 扫码支付
public function scan($order){
return $this->wechat->scan($order)->send();
}
// app支付
public function app($order){
return $this->wechat->app($order)->send();
}
// 小程序支付
public function miniapp($order){
return $this->wechat->miniapp($order);
}
// 公众号支付
public function mp($order){
return $this->wechat->mp($order)->send();
}
}
配置文件 config/cms.php(配置内容换成你的,以下仅供参考,不能使用)
php
// 支付相关
'payment'=>[
// 支付宝支付
'alipay' => [
'notify_url' =>"http://ceshi3.dishait.cn/api/payment/alipay/notify",
'app_id' => '2018091545059522',
'ali_public_key' => "MIIBIcANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhysWmVdABTKzpH3qM40zBo2zGVWNhGe4xm/bFWKnmv/KOtmfZNm8tBjQGQ1o9ME56m0fASnnz5lJS2r+AC4qIePCiKFE5uH57WZo2N9yO042y8VZ/MJsWUgog/FrIvjU3pkp5vB7FcZ1uresolt/jTQPe7Bdoqt6GDetKBCXdEbJbL+DOyYNiCPmVaVH9pv8oy58RjWP5JeBOYIDe3+aMyilHt2sH0Vg/KrvKWzFanoltzwHrgMp27gtvTDPmnIjpkKEVfzevxOHe0q/NpsD+4qWd/6xclfcgd70Y+Dc9dwrq30BfoWJtIytTJJ0qqeNkQOLNeOppzintBXFpgQIDAQAB",
'private_key' => "MIIEpACBAAKCAQEAqld5rwZvdFQYzIMhfx7mn+YxTQ6o2yhoYfpZnQaDH7yHCqfIN8svMJ2PXVSrfMkX+dlkQzsT5nYiujRZ8WJ/RT5Nbh45nAoj+o9id3weWs0qwXU2a7F36LwuIDmvn0zSVoq4zq0wdaCZio/sa7IZ1NaRu/FYuoYINlYZgWYsggecxKBTR6gGb4Cad/9T759L/2xWc0vETvr14KRb+5cnKOc+avR9BAbgRQNBMZcD6fx5DgZXzQ704GRh4W32hL5WXpzOFPvNf1oOTD4brv4KojPEMcfxBUV/tzDk+dr+HpJpLtGV9aRMqgjZumIpCjmPRADIVpgrmjEXYvF64/UXvwIDAQABAoIBAB/GW4ODnszDVzDnERuGZxzSssWeA2+GNRp5ubep3FHSOBqLu7R0qWPXMEQHpEmNtXQ80hAceBTYWpgDZfe6GOWQvp7Y8oQh/B4kGwkQ1RS5SKL8H5MBb1VBjXoYuW+9RsX+1nhKsfl/6WkZeuoR8HNvjM6Wa/e+zdkudwT2GqYD1yuPhSmtlmpjLh0kMBfK4qFbVxbu3XqViWwJOuLwFQl6N258dfp4uMnKNz0nqNQeUfoDZ35Ixe0orPIffypdfdTqOirC7WYbF/8W5Ygtuj/HTQqAIZ+I1qbmI5GfP4Q1S0kkXvj4aq/5r0u9iJOI7GK9U8mFVNu3xVKKsJkCgYEA0sCQmtpSAldXCXFuXSB6RJUKCIw2jmcVLuMfCYBKyGlNUa2jSIKQisXbSQmqYgL7/L6G82GwKjD6ZXvQFAs+c8L/MEkaF2nT6zmMtDSBdIyFIb+bd8kx97EmQjUyd+vf4vDY1zV1cGZa6iF/EHgMHNK4OM5EiX9zvFDGt/3qz8sCgYEAzunatzRCFhQK0tfAV6c0VOrgZntirooa6onH/0sO6Sm1W4vEJoYHkaPJpaOO4V2nbNcLK1620NkqB1RT+5YzMb7jEpL8DY24ufM1dD+aihWHt8DGCRSZdycDcwIKM+f2vwtsvwjldfudRjh9QXWTfALPkSbqpYABoqqKD8PtcV0CgYEAr1pbtwyKCbqkB45itoesU93yEDShvBCW9oExNNWS43eCRsCDyHQiUeTYVMf/BTfYdG4OmPih/CjuXnwLIHJOj0Ei1Qkt9WcvVVt38ARz5gZ5SyBC+gLkWWQDIjli+Za/nPKqaT3orhHr+TzPnWNVKLJHZ7RwIDt0j65h+XsC4csCgYAi3wyqVZdRqz0Lvaq/2wEZ0p/RBbhi3AmfP2tCXj78Erhq3kpHh80cwXLJhKAe4S7HTBKo04SR/Bd2NsMUooKsPpR8W+M40YqxZAi9N77uyKQf1tBJVXxrtVqCdnSLvOs71UwVggVR9f7Sh9CsSIl7m+mpd9qR29nqtT0hXQ7WTQKBgQDP4NIHuAQOc0xbO58HZgtGB4wFLbSG+PFL+VJxlVvJ6oBHR6axiWTFR+yzS/nJC918BCh7HfsQnfePlrydG2o7NDO25jGyQZvbf5welgCSxWaQ1j9rulng4saBrZNgOmUFBWi+YB3U2NY5HILYuw/2UB8sJyv326iqnKLA7hVVzg==",
'log' => [
'file' => './payment/alipay.log',
'level' => 'debug',
'type' => 'single', // optional, 可选 daily.
'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天
],
//'mode' => 'dev' // 进入沙箱模式 不需要的时候需要注释掉
],
// 微信支付
'wechat' => [
'notify_url' => "http://ceshi3.dishait.cn/api/payment/wxpay/notify",
'app_id' => 'wxf0d08abcc66Vab61', // 公众号 APPID
'miniapp_id' => 'wxb1fd05dS99c0fe32', // 小程序 APPID
'appid' => 'wxc55deade7Da3bde', // appid
'mch_id' => '1554008681', // 商户号
'key' => '8b078a1ec793049f1569793464c7049f', // API 密钥
'cert_client' => __DIR__.'/payment/wechat2/apiclient_cert.pem',
'cert_key' => __DIR__.'/payment/wechat2/apiclient_key.pem',
'log' => [
'file' => './payment/wechat_pay.log',
'level' => 'debug', // 建议生产环境等级调整为 info,开发环境为 debug
'type' => 'single', // optional, 可选 daily.
'max_file' => 30,
],
'http' => [ // optional
'timeout' => 5.0,
'connect_timeout' => 5.0,
],
//'mode' => 'dev',
],
],
// 微信小程序相关(微信小程序支付需要配置)
'wx'=>[
'appid'=>'wxb1fd05dS99c0fe32',
'secret'=>'09f38qdf2b1asf9763e9a6924f9ced'
]
控制器 app/controller/common/Payment.php
php
<?php
namespace app\controller\common;
use think\Request;
use app\BaseController;
use Yansongda\Pay\Pay;
class Payment extends BaseController
{
protected $autoNewModel = false;
protected $excludeValidateCheck = ['alipayReturn','alipayNotify','wechatNotify'];
// 支付宝支付
public function payByAlipay(){
// 验证当前订单是否为该用户
$user = request()->UserModel;
$order = request()->Model;
if ($user->id !== $order->user_id) {
ApiException('非法操作');
}
// 当前订单是否已经支付过
if ($order->paid_time || $order->closed) {
ApiException('订单状态不正确');
}
$alipay = new AliPay;
return $alipay->app([
'out_trade_no' => $order->no, // 订单编号,需保证在商户端不重复
'total_amount' => $order->total_price, // 订单金额,单位元,支持小数点后两位
'subject' => '支付ShopAdmin的订单:'.$order->no, // 订单标题
'body' => '支付ShopAdmin的订单:'.$order->no,
]);
}
// 前端回调
public function alipayReturn(){
$obj = new AliPay;
try {
$data = $obj->alipay->verify(request()->param());
} catch (\Exception $e) {
ApiException('数据不正确');
}
return showSuccess('付款成功');
}
// 服务端回调
public function alipayNotify(){
$obj = new AliPay;
$alipay = $obj->alipay;
trace('[支付宝alipayNotify] 拿到支付宝实例','info');
// 校验输入参数
$data = $alipay->verify(request()->param());
trace('[支付宝alipayNotify] 获取到参数'.$data,'info');
// 如果订单状态不是成功或者结束,则不走后续的逻辑
// 所有交易状态:https://docs.open.alipay.com/59/103672
if(!in_array($data->trade_status, ['TRADE_SUCCESS', 'TRADE_FINISHED'])) {
trace('[支付宝alipayNotify] 订单状态不是成功或者结束'.$data,'error');
return $alipay->success()->send();
}
// $data->out_trade_no 拿到订单流水号,并在数据库中查询
$order = \app\model\Order::where('no', $data->out_trade_no)->find();
if (!$order) {
trace('[支付宝alipayNotify] 订单不存在'.$data,'error');
return 'fail';
}
// 如果这笔订单的状态已经是已支付
if ($order->paid_time) {
trace('[支付宝alipayNotify] 这笔订单的状态已经是已支付'.$data,'error');
// 返回数据给支付宝
return $alipay->success()->send();
}
// 更新订单状态
$order->paid_time = time();
$order->payment_method = 'alipay';
$order->payment_no = $data->trade_no;
$order->save();
// 支付成功后的操作
$this->afterPay($order);
trace('[支付宝alipayNotify] 成功'.$data,'info');
return $alipay->success()->send();
}
// 微信小程序支付
public function payByWechatMp(){
$code = request()-> param('code');
if(!$code){
ApiException('code不能为空');
}
// 验证当前订单是否为该用户
$user = request()->UserModel;
$order = request()->Model;
if ($user->id !== $order->user_id) {
ApiException('非法操作');
}
// 当前订单是否已经支付过
if ($order->paid_time || $order->closed) {
ApiException('订单状态不正确');
}
// 获取openid
$openid = $this->getOpenId($code);
// 调用支付
$wechat = new wechatPay;
return $wechat->miniapp([
'out_trade_no' => $order->no, // 订单编号,需保证在商户端不重复
'body' => '支付ShopAdmin的订单:'.$order->no, // 订单标题
'total_fee' => strval($order->total_price*100), // 订单金额,单位分,支持小数点后两位
'openid' => $openid,
]);
}
// 获取openid
public function getOpenId($code){
$url = "https://api.weixin.qq.com/sns/jscode2session";
// 参数
$params['appid']= config('cms.wx.appid');
$params['secret']= config('cms.wx.secret');
$params['js_code']= $code;
$params['grant_type']= 'authorization_code';
// 微信API返回的session_key 和 openid
$arr = httpWurl($url, $params, 'POST');
$arr = json_decode($arr,true);
// 不成功
if(isset($arr['errcode']) && !empty($arr['errcode'])){
ApiException($arr['errmsg']);
}
// 拿到数据
return $arr['openid'];
}
// 微信支付
public function payByWechat(){
// 验证当前订单是否为该用户
$user = request()->UserModel;
$order = request()->Model;
if ($user->id !== $order->user_id) {
ApiException('非法操作');
}
// 当前订单是否已经支付过
if ($order->paid_time || $order->closed) {
ApiException('订单状态不正确');
}
// 调用支付
$wechat = new wechatPay;
return $wechat->app([
'out_trade_no' => $order->no, // 订单编号,需保证在商户端不重复
'body' => '支付ShopAdmin的订单:'.$order->no, // 订单标题
'total_fee' => strval($order->total_price*100), // 订单金额,单位分,支持小数点后两位
]);
}
/**
* 微信支付回调
* @param Request $request
*/
public function wechatNotify(Request $request){
trace('[微信支付回调] 回调开始','info');
// 此代码 是因为 php7之后的版本中已经弃用 HTTP_RAW_POST_DATA,所以...
$xml=isset($GLOBALS['HTTP_RAW_POST_DATA'])
? $GLOBALS['HTTP_RAW_POST_DATA'] : file_get_contents("php://input");
//将服务器返回的XML数据转化为数组
$data = self::xml2array($xml);
if (($data['return_code'] == 'SUCCESS') && ($data['result_code'] == 'SUCCESS')) {
trace('[微信支付回调] 支付成功开始处理回调','info');
// //获取服务器返回的数据
// $out_trade_no = $data['out_trade_no']; //订单单号
// $data['timestamp'] = date("Y-m-d H:i:s");
// $json_str_notify = json_encode($data);
$out_trade_no = $data['out_trade_no'];
$order = \app\model\Order::where('no', $out_trade_no)->find();
if ($order) {
// 如果这笔订单的状态已经是已支付
if (!$order->paid_time) {
// 更新订单状态
$order->paid_time = time();
$order->payment_method = 'wechat';
$order->payment_no = $out_trade_no;
$order->save();
trace('[微信支付回调] 成功','info');
}
} else {
trace('[微信支付回调] 订单不存在'.$out_trade_no,'error');
}
} else {
trace('[微信支付回调] 支付失败','info');
}
}
/**
* 将xml转为array
* @param string $xml xml字符串
* @return array 转换得到的数组
*/
protected function xml2array($xml)
{
//禁止引用外部xml实体
libxml_disable_entity_loader(true);
$result = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
return $result;
}
// 支付成功后操作
public function afterPay($order){
// 商品增加销量
$this->addSaleCount($order);
}
// 增加商品销量
public function addSaleCount($order){
// 获取当前订单对应商品
$orderItems = $order->orderItems;
$orderItems->each(function($item){
// 需要增加的销量
$count = $item->num;
// 获取每个订单的商品
$goods = $item->skus_type === 1 ? $item->goodsSkus->goods : $item->goods;
// 更新商品销量
$goods->sale_count += $count;
$goods->save();
});
}
}
验证器 app/validate/admin/Order.php
php
// 验证规则
protected $rule = [
// ...
'id'=>'require|integer|>:0|isExist:Order',
'agree'=>'require|in:0,1',
'disagree_reason|拒绝退款理由'=>'requireIf:agree,0',
// ...
];
protected $scene = [
// ...
'handleRefund'=>['id','agree','disagree_reason'],
];
路由 router/admin.php
php
Route::post('order/:id/handle_refund','admin.Order/handleRefund');