mirror of
https://gitee.com/technical-laohu/mpay_v2_webman.git
synced 2026-04-22 01:54:25 +08:00
1. 完善易支付API调用全流程
2. 确定支付插件继承基础类和接口规范 3. 引入Yansongda\Pay支付快捷工具 4. 重新整理代码和功能结构
This commit is contained in:
@@ -6,6 +6,7 @@ use app\common\base\BaseService;
|
||||
use app\exceptions\{BadRequestException, NotFoundException};
|
||||
use app\models\PaymentOrder;
|
||||
use app\repositories\{MerchantAppRepository, PaymentChannelRepository, PaymentMethodRepository, PaymentOrderRepository};
|
||||
use Illuminate\Database\QueryException;
|
||||
|
||||
/**
|
||||
* 支付订单服务
|
||||
@@ -28,11 +29,11 @@ class PayOrderService extends BaseService
|
||||
public function createOrder(array $data)
|
||||
{
|
||||
// 1. 基本参数校验
|
||||
$mchId = (int)($data['mch_id'] ?? $data['merchant_id'] ?? 0);
|
||||
$mchId = (int)($data['mch_id'] ?? 0);
|
||||
$appId = (int)($data['app_id'] ?? 0);
|
||||
$mchNo = trim((string)($data['mch_no'] ?? $data['mch_order_no'] ?? ''));
|
||||
$methodCode = trim((string)($data['method_code'] ?? ''));
|
||||
$amount = (float)($data['amount'] ?? 0);
|
||||
$mchNo = trim((string)($data['mch_order_no'] ?? ''));
|
||||
$payType = trim((string)($data['pay_type'] ?? ''));
|
||||
$amountFloat = (float)($data['amount'] ?? 0);
|
||||
$subject = trim((string)($data['subject'] ?? ''));
|
||||
|
||||
if ($mchId <= 0 || $appId <= 0) {
|
||||
@@ -41,10 +42,10 @@ class PayOrderService extends BaseService
|
||||
if ($mchNo === '') {
|
||||
throw new BadRequestException('商户订单号不能为空');
|
||||
}
|
||||
if ($methodCode === '') {
|
||||
if ($payType === '') {
|
||||
throw new BadRequestException('支付方式不能为空');
|
||||
}
|
||||
if ($amount <= 0) {
|
||||
if ($amountFloat <= 0) {
|
||||
throw new BadRequestException('订单金额必须大于0');
|
||||
}
|
||||
if ($subject === '') {
|
||||
@@ -52,12 +53,13 @@ class PayOrderService extends BaseService
|
||||
}
|
||||
|
||||
// 2. 查询支付方式ID
|
||||
$method = $this->methodRepository->findByCode($methodCode);
|
||||
$method = $this->methodRepository->findByCode($payType);
|
||||
if (!$method) {
|
||||
throw new BadRequestException('支付方式不存在');
|
||||
}
|
||||
|
||||
// 3. 幂等校验:同一商户应用下相同商户订单号只保留一条
|
||||
// 先查一次(减少异常成本),并发场景再用唯一键冲突兜底
|
||||
$existing = $this->orderRepository->findByMchNo($mchId, $appId, $mchNo);
|
||||
if ($existing) {
|
||||
return $existing;
|
||||
@@ -65,25 +67,34 @@ class PayOrderService extends BaseService
|
||||
|
||||
// 4. 生成系统订单号
|
||||
$orderId = $this->generateOrderId();
|
||||
$amount = sprintf('%.2f', $amountFloat);
|
||||
|
||||
// 5. 创建订单
|
||||
return $this->orderRepository->create([
|
||||
'order_id' => $orderId,
|
||||
'merchant_id' => $mchId,
|
||||
'merchant_app_id' => $appId,
|
||||
'mch_order_no' => $mchNo,
|
||||
'method_id' => $method->id,
|
||||
'channel_id' => $data['channel_id'] ?? $data['chan_id'] ?? 0,
|
||||
'amount' => $amount,
|
||||
'real_amount' => $amount,
|
||||
'fee' => $data['fee'] ?? 0.00,
|
||||
'subject' => $subject,
|
||||
'body' => $data['body'] ?? $subject,
|
||||
'status' => PaymentOrder::STATUS_PENDING,
|
||||
'client_ip' => $data['client_ip'] ?? '',
|
||||
'expire_at' => $data['expire_at'] ?? $data['expire_time'] ?? date('Y-m-d H:i:s', time() + 1800),
|
||||
'extra' => $data['extra'] ?? [],
|
||||
]);
|
||||
$expireTime = (int)sys_config('order_expire_time', 0); // 0 表示不设置过期时间
|
||||
try {
|
||||
return $this->orderRepository->create([
|
||||
'order_id' => $orderId,
|
||||
'merchant_id' => $mchId,
|
||||
'merchant_app_id' => $appId,
|
||||
'mch_order_no' => $mchNo,
|
||||
'method_id' => $method->id,
|
||||
'amount' => $amount,
|
||||
'real_amount' => $amount,
|
||||
'subject' => $subject,
|
||||
'body' => $data['body'] ?? $subject,
|
||||
'status' => PaymentOrder::STATUS_PENDING,
|
||||
'client_ip' => $data['client_ip'] ?? '',
|
||||
'expire_at' => $expireTime > 0 ? date('Y-m-d H:i:s', time() + $expireTime) : null,
|
||||
'extra' => $data['extra'] ?? [],
|
||||
]);
|
||||
} catch (QueryException $e) {
|
||||
// 并发场景:唯一键 uk_mch_order 冲突时回查返回已有订单
|
||||
$existing = $this->orderRepository->findByMchNo($mchId, $appId, $mchNo);
|
||||
if ($existing) {
|
||||
return $existing;
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,7 +153,7 @@ class PayOrderService extends BaseService
|
||||
$channel->getConfigArray(),
|
||||
['enabled_products' => $channel->getEnabledProducts()]
|
||||
);
|
||||
$plugin->init($method->method_code, $channelConfig);
|
||||
$plugin->init($channelConfig);
|
||||
|
||||
// 7. 调用插件退款
|
||||
$refundData = [
|
||||
@@ -153,7 +164,7 @@ class PayOrderService extends BaseService
|
||||
'refund_reason' => $data['refund_reason'] ?? '',
|
||||
];
|
||||
|
||||
$refundResult = $plugin->refund($refundData, $channelConfig);
|
||||
$refundResult = $plugin->refund($refundData);
|
||||
|
||||
// 8. 如果是全额退款则关闭订单
|
||||
if ($refundAmount >= $order->amount) {
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
namespace app\services;
|
||||
|
||||
use app\common\base\BaseService;
|
||||
use app\common\contracts\PayPluginInterface;
|
||||
use app\exceptions\NotFoundException;
|
||||
use app\models\PaymentOrder;
|
||||
use app\repositories\{PaymentMethodRepository, PaymentOrderRepository};
|
||||
use app\common\contracts\AbstractPayPlugin;
|
||||
use support\Request;
|
||||
|
||||
/**
|
||||
@@ -37,7 +37,7 @@ class PayService extends BaseService
|
||||
* - mch_no
|
||||
* - pay_params
|
||||
*/
|
||||
public function unifiedPay(array $orderData, array $options = []): array
|
||||
public function pay(array $orderData, array $options = []): array
|
||||
{
|
||||
// 1. 创建订单(幂等)
|
||||
/** @var PaymentOrder $order */
|
||||
@@ -63,7 +63,7 @@ class PayService extends BaseService
|
||||
$channel->getConfigArray(),
|
||||
['enabled_products' => $channel->getEnabledProducts()]
|
||||
);
|
||||
$plugin->init($method->method_code, $channelConfig);
|
||||
$plugin->init($channelConfig);
|
||||
|
||||
// 5. 环境检测
|
||||
$device = $options['device'] ?? '';
|
||||
@@ -75,23 +75,27 @@ class PayService extends BaseService
|
||||
} elseif ($request instanceof Request) {
|
||||
$env = $this->detectEnvironment($request);
|
||||
} else {
|
||||
$env = AbstractPayPlugin::ENV_PC;
|
||||
$env = 'pc';
|
||||
}
|
||||
|
||||
// 6. 调用插件统一下单
|
||||
$pluginOrderData = [
|
||||
'order_id' => $order->order_id,
|
||||
'mch_no' => $order->mch_order_no,
|
||||
'amount' => $order->amount,
|
||||
'subject' => $order->subject,
|
||||
'body' => $order->body,
|
||||
'mch_no' => $order->mch_order_no,
|
||||
'amount' => $order->amount,
|
||||
'subject' => $order->subject,
|
||||
'body' => $order->body,
|
||||
'extra' => $order->extra ?? [],
|
||||
'_env' => $env,
|
||||
];
|
||||
|
||||
$payResult = $plugin->unifiedOrder($pluginOrderData, $channelConfig, $env);
|
||||
$payResult = $plugin->pay($pluginOrderData);
|
||||
|
||||
// 7. 计算实际支付金额(扣除手续费)
|
||||
$fee = $order->fee > 0 ? $order->fee : ($order->amount * ($channel->chan_cost / 100));
|
||||
$realAmount = $order->amount - $fee;
|
||||
$amount = (float)$order->amount;
|
||||
$chanCost = (float)$channel->chan_cost;
|
||||
$fee = ((float)$order->fee) > 0 ? (float)$order->fee : round($amount * ($chanCost / 100), 2);
|
||||
$realAmount = round($amount - $fee, 2);
|
||||
|
||||
// 8. 更新订单(通道、支付参数、实际金额)
|
||||
$extra = $order->extra ?? [];
|
||||
@@ -103,8 +107,8 @@ class PayService extends BaseService
|
||||
'channel_id' => $channel->id,
|
||||
'chan_order_no' => $chanOrderNo,
|
||||
'chan_trade_no' => $chanTradeNo,
|
||||
'real_amount' => $realAmount,
|
||||
'fee' => $fee,
|
||||
'real_amount' => sprintf('%.2f', $realAmount),
|
||||
'fee' => sprintf('%.2f', $fee),
|
||||
'extra' => $extra,
|
||||
]);
|
||||
|
||||
@@ -123,21 +127,21 @@ class PayService extends BaseService
|
||||
$ua = strtolower($request->header('User-Agent', ''));
|
||||
|
||||
if (strpos($ua, 'alipayclient') !== false) {
|
||||
return AbstractPayPlugin::ENV_ALIPAY_CLIENT;
|
||||
return 'alipay';
|
||||
}
|
||||
|
||||
if (strpos($ua, 'micromessenger') !== false) {
|
||||
return AbstractPayPlugin::ENV_WECHAT;
|
||||
return 'wechat';
|
||||
}
|
||||
|
||||
$mobileKeywords = ['mobile', 'android', 'iphone', 'ipad', 'ipod', 'blackberry', 'windows phone'];
|
||||
foreach ($mobileKeywords as $keyword) {
|
||||
if (strpos($ua, $keyword) !== false) {
|
||||
return AbstractPayPlugin::ENV_H5;
|
||||
return 'h5';
|
||||
}
|
||||
}
|
||||
|
||||
return AbstractPayPlugin::ENV_PC;
|
||||
return 'pc';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,15 +150,15 @@ class PayService extends BaseService
|
||||
private function mapDeviceToEnv(string $device): string
|
||||
{
|
||||
$mapping = [
|
||||
'pc' => AbstractPayPlugin::ENV_PC,
|
||||
'mobile' => AbstractPayPlugin::ENV_H5,
|
||||
'qq' => AbstractPayPlugin::ENV_H5,
|
||||
'wechat' => AbstractPayPlugin::ENV_WECHAT,
|
||||
'alipay' => AbstractPayPlugin::ENV_ALIPAY_CLIENT,
|
||||
'jump' => AbstractPayPlugin::ENV_PC,
|
||||
'pc' => 'pc',
|
||||
'mobile' => 'h5',
|
||||
'qq' => 'h5',
|
||||
'wechat' => 'wechat',
|
||||
'alipay' => 'alipay',
|
||||
'jump' => 'pc',
|
||||
];
|
||||
|
||||
return $mapping[strtolower($device)] ?? AbstractPayPlugin::ENV_PC;
|
||||
return $mapping[strtolower($device)] ?? 'pc';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
namespace app\services;
|
||||
|
||||
use app\common\base\BaseService;
|
||||
use app\common\contracts\AbstractPayPlugin;
|
||||
use app\common\contracts\PaymentInterface;
|
||||
use app\common\contracts\PayPluginInterface;
|
||||
use app\exceptions\NotFoundException;
|
||||
use app\repositories\PaymentPluginRepository;
|
||||
|
||||
@@ -36,8 +37,8 @@ class PluginService extends BaseService
|
||||
$plugin = $this->resolvePlugin($pluginCode, $row->class_name);
|
||||
$plugins[] = [
|
||||
'code' => $pluginCode,
|
||||
'name' => $plugin::getName(),
|
||||
'supported_methods'=> $plugin::getSupportedMethods(),
|
||||
'name' => $plugin->getName(),
|
||||
'supported_methods'=> $plugin->getEnabledPayTypes(),
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
// 忽略无法实例化的插件
|
||||
@@ -54,7 +55,7 @@ class PluginService extends BaseService
|
||||
public function getConfigSchema(string $pluginCode, string $methodCode): array
|
||||
{
|
||||
$plugin = $this->getPluginInstance($pluginCode);
|
||||
return $plugin::getConfigSchema($methodCode);
|
||||
return $plugin->getConfigSchema();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,8 +63,12 @@ class PluginService extends BaseService
|
||||
*/
|
||||
public function getSupportedProducts(string $pluginCode, string $methodCode): array
|
||||
{
|
||||
/** @var mixed $plugin */
|
||||
$plugin = $this->getPluginInstance($pluginCode);
|
||||
return $plugin::getSupportedProducts($methodCode);
|
||||
if (method_exists($plugin, 'getSupportedProducts')) {
|
||||
return (array)$plugin->getSupportedProducts($methodCode);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,7 +77,7 @@ class PluginService extends BaseService
|
||||
public function buildConfigFromForm(string $pluginCode, string $methodCode, array $formData): array
|
||||
{
|
||||
$plugin = $this->getPluginInstance($pluginCode);
|
||||
$configSchema = $plugin::getConfigSchema($methodCode);
|
||||
$configSchema = $plugin->getConfigSchema();
|
||||
|
||||
$configJson = [];
|
||||
if (isset($configSchema['fields']) && is_array($configSchema['fields'])) {
|
||||
@@ -90,7 +95,7 @@ class PluginService extends BaseService
|
||||
/**
|
||||
* 对外统一提供:根据插件编码获取插件实例
|
||||
*/
|
||||
public function getPluginInstance(string $pluginCode): AbstractPayPlugin
|
||||
public function getPluginInstance(string $pluginCode): PaymentInterface&PayPluginInterface
|
||||
{
|
||||
$row = $this->pluginRepository->findActiveByCode($pluginCode);
|
||||
if (!$row) {
|
||||
@@ -103,7 +108,7 @@ class PluginService extends BaseService
|
||||
/**
|
||||
* 根据插件编码和 class_name 解析并实例化插件
|
||||
*/
|
||||
private function resolvePlugin(string $pluginCode, ?string $className = null): AbstractPayPlugin
|
||||
private function resolvePlugin(string $pluginCode, ?string $className = null): PaymentInterface&PayPluginInterface
|
||||
{
|
||||
$class = $className ?: 'app\\common\\payment\\' . ucfirst($pluginCode) . 'Payment';
|
||||
|
||||
@@ -112,7 +117,7 @@ class PluginService extends BaseService
|
||||
}
|
||||
|
||||
$plugin = new $class();
|
||||
if (!$plugin instanceof AbstractPayPlugin) {
|
||||
if (!$plugin instanceof PaymentInterface || !$plugin instanceof PayPluginInterface) {
|
||||
throw new NotFoundException('支付插件类型错误:' . $class);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
namespace app\services\api;
|
||||
|
||||
use app\common\base\BaseService;
|
||||
use app\common\utils\EpayUtil;
|
||||
use app\services\PayOrderService;
|
||||
use app\services\PayService;
|
||||
use app\repositories\{MerchantAppRepository, PaymentMethodRepository, PaymentOrderRepository};
|
||||
use app\models\PaymentOrder;
|
||||
use app\exceptions\{BadRequestException, NotFoundException};
|
||||
use app\exceptions\{BadRequestException, NotFoundException, UnauthorizedException};
|
||||
use support\Request;
|
||||
|
||||
/**
|
||||
@@ -37,7 +38,7 @@ class EpayService extends BaseService
|
||||
throw new BadRequestException('暂不支持收银台模式,请指定支付方式 type');
|
||||
}
|
||||
|
||||
return $this->createUnifiedOrder($data, $request);
|
||||
return $this->createOrder($data, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,7 +50,7 @@ class EpayService extends BaseService
|
||||
*/
|
||||
public function mapi(array $data, Request $request): array
|
||||
{
|
||||
$result = $this->createUnifiedOrder($data, $request);
|
||||
$result = $this->createOrder($data, $request);
|
||||
$payParams = $result['pay_params'] ?? [];
|
||||
|
||||
$response = [
|
||||
@@ -142,7 +143,7 @@ class EpayService extends BaseService
|
||||
'trade_no' => $order->order_id,
|
||||
'out_trade_no' => $order->mch_order_no,
|
||||
'api_trade_no' => $order->chan_trade_no ?? '',
|
||||
'type' => $this->mapMethodToEpayType($methodCode),
|
||||
'type' => $methodCode,
|
||||
'pid' => (int)$pid,
|
||||
'addtime' => $order->created_at,
|
||||
'endtime' => $order->pay_at,
|
||||
@@ -210,11 +211,11 @@ class EpayService extends BaseService
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
private function createUnifiedOrder(array $data, Request $request): array
|
||||
private function createOrder(array $data, Request $request): array
|
||||
{
|
||||
$pid = (int)($data['pid'] ?? 0);
|
||||
if ($pid <= 0) {
|
||||
throw new BadRequestException('商户ID不能为空');
|
||||
throw new BadRequestException('应用ID不能为空');
|
||||
}
|
||||
|
||||
// 根据 pid 映射应用(约定 pid = app_id)
|
||||
@@ -223,14 +224,21 @@ class EpayService extends BaseService
|
||||
throw new NotFoundException('商户应用不存在或已禁用');
|
||||
}
|
||||
|
||||
$methodCode = $this->mapEpayTypeToMethod($data['type'] ?? '');
|
||||
// 易支付签名校验:使用 app_secret 作为 key
|
||||
$signType = strtolower((string)($data['sign_type'] ?? 'md5'));
|
||||
if ($signType !== 'md5') {
|
||||
throw new BadRequestException('不支持的签名类型:' . ($data['sign_type'] ?? ''));
|
||||
}
|
||||
if (!EpayUtil::verify($data, (string)$app->app_secret)) {
|
||||
throw new UnauthorizedException('签名验证失败');
|
||||
}
|
||||
|
||||
$orderData = [
|
||||
'merchant_id' => $app->merchant_id,
|
||||
'mch_id' => $app->merchant_id,
|
||||
'app_id' => $app->id,
|
||||
'mch_order_no' => $data['out_trade_no'],
|
||||
'method_code' => $methodCode,
|
||||
'amount' => (float)$data['money'],
|
||||
'currency' => 'CNY',
|
||||
'pay_type' => $data['type'],
|
||||
'amount' => sprintf('%.2f', (float)$data['money']),
|
||||
'subject' => $data['name'],
|
||||
'body' => $data['name'],
|
||||
'client_ip' => $data['clientip'] ?? $request->getRemoteIp(),
|
||||
@@ -242,26 +250,12 @@ class EpayService extends BaseService
|
||||
];
|
||||
|
||||
// 调用通用支付服务完成通道选择与插件下单
|
||||
return $this->payService->unifiedPay($orderData, [
|
||||
return $this->payService->pay($orderData, [
|
||||
'device' => $data['device'] ?? '',
|
||||
'request' => $request,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 映射易支付 type 到内部 method_code
|
||||
*/
|
||||
private function mapEpayTypeToMethod(string $type): string
|
||||
{
|
||||
$mapping = [
|
||||
'alipay' => 'alipay',
|
||||
'wxpay' => 'wechat',
|
||||
'qqpay' => 'qq',
|
||||
];
|
||||
|
||||
return $mapping[$type] ?? $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据订单获取支付方式编码
|
||||
*/
|
||||
@@ -270,19 +264,4 @@ class EpayService extends BaseService
|
||||
$method = $this->methodRepository->find($order->method_id);
|
||||
return $method ? $method->method_code : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 映射内部 method_code 到易支付 type
|
||||
*/
|
||||
private function mapMethodToEpayType(string $methodCode): string
|
||||
{
|
||||
$mapping = [
|
||||
'alipay' => 'alipay',
|
||||
'wechat' => 'wxpay',
|
||||
'qq' => 'qqpay',
|
||||
];
|
||||
|
||||
return $mapping[$methodCode] ?? $methodCode;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user