Browse Source

feat(pay): 实现支付回调与订单管理功能

- 新增支付回调接口,支持微信和支付宝回调处理
- 创建支付订单模型及数据访问对象
- 实现订单支付成功后的状态更新逻辑
- 添加用户余额自动增加功能
- 实现唯一订单号生成功能,包含时间、机器标识和随机字符串
- 引入随机字符串工具类方法
- 修改支付工具类依赖注入,使用 PayService 处理回调
- 调整支付金额精度处理逻辑,支持调试模式小额测试
- 完善支付异常处理机制,增加订单不存在等校验逻辑
master
zhangf@suq.cn 8 hours ago
parent
commit
2ea7803658
  1. 13
      plugin/piadmin/app/controller/v1/pay/PayController.php
  2. 16
      plugin/piadmin/app/dao/PayOrderDao.php
  3. 13
      plugin/piadmin/app/model/PayOrder.php
  4. 2
      plugin/piadmin/app/route/v1/pay.php
  5. 152
      plugin/piadmin/app/service/pay/PayService.php
  6. 19
      plugin/piadmin/app/utils/RandomUtils.php
  7. 5
      plugin/piadmin/app/utils/pay/ali/AliPay.php
  8. 5
      plugin/piadmin/app/utils/pay/wechat/WechatPay.php

13
plugin/piadmin/app/controller/v1/pay/PayController.php

@ -3,6 +3,7 @@
namespace plugin\piadmin\app\controller\v1\pay;
use plugin\piadmin\app\base\BaseController;
use plugin\piadmin\app\exception\ApiException;
use plugin\piadmin\app\service\pay\PayService;
class PayController extends BaseController
@ -19,5 +20,15 @@ class PayController extends BaseController
return success($qrurl);
}
/**
* 支付回调
*/
public function notify(PayService $payService, $type)
{
$result = $payService->notify($type);
if (!empty($result)) {
return success();
}
throw new ApiException('支付失败');
}
}

16
plugin/piadmin/app/dao/PayOrderDao.php

@ -0,0 +1,16 @@
<?php
namespace plugin\piadmin\app\dao;
use plugin\piadmin\app\base\BaseDao;
use plugin\piadmin\app\model\PayOrder;
class PayOrderDao extends BaseDao
{
protected function setModel(): string
{
return PayOrder::class;
}
}

13
plugin/piadmin/app/model/PayOrder.php

@ -0,0 +1,13 @@
<?php
namespace plugin\piadmin\app\model;
use plugin\piadmin\app\base\BaseModel;
class PayOrder extends BaseModel
{
protected $table = 'pi_pay_order';
protected $autoWriteTimestamp = true;
}

2
plugin/piadmin/app/route/v1/pay.php

@ -11,7 +11,7 @@ Route::group('/piadmin/v1/pay', function () {
Route::post('/getQrUrl', [PayController::class, 'getQrUrl']);
//回调
Route::any('/notify/:type', [PayController::class, 'notify']);
})->middleware([
UserAuthorizationMiddleware::class,
]);

152
plugin/piadmin/app/service/pay/PayService.php

@ -3,25 +3,56 @@
namespace plugin\piadmin\app\service\pay;
use plugin\piadmin\app\base\BaseService;
use plugin\piadmin\app\dao\PayOrderDao;
use plugin\piadmin\app\dao\UserDao;
use plugin\piadmin\app\exception\ApiException;
use plugin\piadmin\app\utils\pay\ali\AliPay;
use plugin\piadmin\app\utils\pay\wechat\WechatPay;
use plugin\piadmin\app\utils\RandomUtils;
use plugin\piadmin\app\utils\RequestUtils;
use support\Db;
class PayService extends BaseService
{
protected $payOrderDao;
protected $userDao;
public function __construct()
{
$this->payOrderDao = app()->make(PayOrderDao::class);
$this->userDao = app()->make(UserDao::class);
}
public function getQrUrl($params)
{
$userInfo = RequestUtils::getUserInfo();
//创建订单记录
$order_no = $this->generateOrderNo();
$price = $params['price'];
if (env('APP_DEBUG')) {
$price = 0.01;
}
$this->payOrderDao->save([
'uid' => $userInfo['id'],
'order_no' => $order_no,
'desc' => '投稿点数充值',
'order_time' => date('Y-m-d H:i:s'),
'total_price' => $params['price'],
'ip_address' => request()->getRealIp()
]);
//微信订单封装
$wechatOrder = [
'out_trade_no' => time().'',
'description' => 'subject-测试',
'out_trade_no' => $order_no,
'description' => '投稿点数充值',
'amount' => [
'total' => $params['price'] * 100,
'total' => $price * 100,
],
];
//支付宝订单封装
$aliOrder = [
'out_trade_no' => time(),
'total_amount' => $params['price'] . '',
'subject' => 'yansongda 测试 - 01',
'out_trade_no' => $order_no,
'total_amount' => $price . '',
'subject' => '投稿点数充值',
];
$wechatqrurl = WechatPay::getQrUrl($wechatOrder);
$aliqrurl = AliPay::getQrUrl($aliOrder);
@ -31,4 +62,111 @@ class PayService extends BaseService
];
}
public function notify($type = 'wechat')
{
switch ($type) {
case 'wechat':
$result = WechatPay::notify();
break;
case 'alipay':
$result = AliPay::notify();
break;
default:
$result = [];
}
return $result;
}
/**
* 订单支付回调
* @param array $params
* @return array
*/
public function orderPayNotify(array $params): array
{
// 查订单
$order = $this->payOrderDao->getOne([
'no' => $params['order_no'],
'delete_time' => 0
]);
if (empty($order)) {
throw new ApiException('订单不存在');
}
if ($order['status'] != 1) {
throw new ApiException('订单状态不需要处理');
}
// 更新订单信息
$updateOrderData = [
'payer_open_id' => $params['payer_open_id'] ?? '',
'transaction_id' => $params['transaction_id'] ?? '',
'pay_type' => $params['pay_type']
];
if ($params['is_paid']) {
$updateOrderData['status'] = 3;
$updateOrderData['pay_price'] = $params['pay_total'] / 100;
$updateOrderData['pay_time'] = $params['success_time']->getTimestamp();
$updateOrderData['pay_currency'] = $params['currency'];
}
// 落库
Db::beginTransaction();
try {
// 订单主表落库
$this->dao->update(['id' => $order['id']], $updateOrderData);
//更改用户余额
$this->userDao->update(['id' => $order['uid']], [
'balance' => Db::raw('balance+' . $params['pay_total'] / 100)
]);
Db::commit();
} catch (\Exception $exception) {
Db::rollback();
}
return [];
}
/**
* 生成唯一订单号
*
* @param string $prefix 业务前缀(如支付方式、渠道标识)
* @param int $length 订单号总长度(默认32位,最小16位)
* @return string
*/
private function generateOrderNo(string $prefix = 'GEOCN', int $length = 32): string
{
// 验证参数
if ($length < 16) {
throw new ApiException("订单号长度不能小于16位");
}
// 1. 基础时间部分(14位:年月日时分秒)
$time = date('YmdHis');
// 2. 随机字符串(至少2位)
$randomLength = $length - strlen($prefix) - strlen($time) - 2;
$randomLength = max($randomLength, 2); // 确保至少2位随机数
$random = RandomUtils::generateRandomString($randomLength);
// 3. 进程/机器标识(2位,避免集群环境冲突)
$machineId = self::getMachineId();
// 组合订单号
$orderNo = $prefix . $time . $machineId . $random;
// 截断或补位到指定长度
return substr($orderNo, 0, $length);
}
/**
* 获取机器/进程标识(2位)
*
* @return string
*/
private function getMachineId(): string
{
// 单机环境:使用进程ID的后两位
// 集群环境:可改为服务器IP最后一段的哈希值
$pid = getmypid() ?: 1;
$id = $pid % 100;
return str_pad((string)$id, 2, '0', STR_PAD_LEFT);
}
}

19
plugin/piadmin/app/utils/RandomUtils.php

@ -47,4 +47,23 @@ class RandomUtils
$hex_part = substr($hash, 0, 8);
return hexdec($hex_part);
}
/**
* 生成指定长度的随机字符串
*
* @param int $length
* @return string
*/
public static function generateRandomString(int $length): string
{
$chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charLength = strlen($chars);
$random = '';
for ($i = 0; $i < $length; $i++) {
$random .= $chars[mt_rand(0, $charLength - 1)];
}
return $random;
}
}

5
plugin/piadmin/app/utils/pay/ali/AliPay.php

@ -1,6 +1,7 @@
<?php
namespace plugin\piadmin\app\utils\pay\ali;
use plugin\piadmin\app\service\pay\PayService;
use plugin\piadmin\app\utils\pay\PayUtils;
use Yansongda\Pay\Pay;
@ -39,8 +40,8 @@ class AliPay {
$data['currency'] = 'CNY';
$data['pay_notify_message'] = json_encode($result);
// $orderService = app()->make(OrderService::class);
// return $orderService->orderPayNotify($data);
$payService = app()->make(PayService::class);
return $payService->orderPayNotify($data);
}
public static function refund($order)

5
plugin/piadmin/app/utils/pay/wechat/WechatPay.php

@ -1,6 +1,7 @@
<?php
namespace plugin\piadmin\app\utils\pay\wechat;
use plugin\piadmin\app\service\pay\PayService;
use plugin\piadmin\app\utils\pay\PayUtils;
use Yansongda\Pay\Pay;
@ -36,8 +37,8 @@ class WechatPay {
$data['pay_notify_message'] = json_encode($result);
// $orderService = app()->make(OrderService::class);
// return $orderService->orderPayNotify($data);
$payService = app()->make(PayService::class);
return $payService->orderPayNotify($data);
}
public static function refund($order)

Loading…
Cancel
Save