RechargeChannelConfigurationService.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. <?php
  2. namespace App\Services\Payment;
  3. use App\Models\RechargeChannel;
  4. use App\Models\RechargeChannelGroup;
  5. use App\Services\OperationAuditService;
  6. use Illuminate\Support\Facades\DB;
  7. use Illuminate\Support\Facades\Schema;
  8. use RuntimeException;
  9. class RechargeChannelConfigurationService
  10. {
  11. public function options(int $dataType, ?int $id = null): array
  12. {
  13. $options = PaymentProviderCatalog::channelFormOptions($dataType, PaymentProviderService::statuses());
  14. $reason = '';
  15. if ($id) {
  16. $channel = RechargeChannel::query()->findOrFail($id);
  17. if ((int)$channel->data_type !== $dataType) throw new RuntimeException('通道方向与编辑记录不一致');
  18. $reason = $this->identityLockReason($channel);
  19. }
  20. return $options + ['identity_editable' => $reason === '', 'identity_edit_reason' => $reason];
  21. }
  22. public function save(array $params): RechargeChannel
  23. {
  24. return DB::transaction(function () use ($params) {
  25. $id = (int)($params['id'] ?? 0);
  26. unset($params['id']);
  27. $channel = $id ? RechargeChannel::query()->lockForUpdate()->findOrFail($id) : new RechargeChannel();
  28. if ($channel->exists && (int)$channel->data_type !== (int)$params['data_type']) {
  29. throw new RuntimeException('不能修改通道所属的充值、提现或活动方向');
  30. }
  31. $params = PaymentProviderCatalog::normalizeChannelSelection($params);
  32. $identityChanged = !$channel->exists || (int)$channel->from !== $params['from'] || (string)$channel->type !== (string)$params['type'];
  33. $identity = PaymentProviderCatalog::channelIdentity($params);
  34. if ($identityChanged && $identity) PaymentProviderService::assertEnabled($identity['provider_code']);
  35. if ($channel->exists && ((int)$channel->from !== $params['from'] || (string)$channel->type !== (string)$params['type'])) {
  36. $reason = $this->identityLockReason($channel, true);
  37. if ($reason !== '') throw new RuntimeException($reason);
  38. }
  39. foreach (['name', 'key'] as $field) {
  40. if (array_key_exists($field, $params)) $params[$field] = trim((string)$params[$field]);
  41. if ((!$channel->exists || array_key_exists($field, $params)) && ($params[$field] ?? '') === '') {
  42. throw new RuntimeException($field === 'name' ? '请填写通道名称' : '请填写通道标识Key');
  43. }
  44. }
  45. foreach (['status', 'sort'] as $field) {
  46. if (array_key_exists($field, $params) && $params[$field] === null) unset($params[$field]);
  47. }
  48. if (!$channel->exists) $params += ['status' => 1, 'sort' => 0];
  49. $channel->fill($params);
  50. $channel->save();
  51. return $channel;
  52. }, 3);
  53. }
  54. public function delete(int $id): void
  55. {
  56. DB::transaction(function () use ($id) {
  57. $channel = RechargeChannel::withTrashed()->lockForUpdate()->findOrFail($id);
  58. if (!in_array((int)$channel->data_type, [1, 2], true)) throw new RuntimeException('只支持删除充值或提现通道');
  59. if ($channel->trashed()) return;
  60. if ($this->identityLockReason($channel, true) !== '') {
  61. throw new RuntimeException('该通道已被支付配置引用,请先解除关联再删除');
  62. }
  63. $before = $channel->toArray();
  64. $channel->status = 0;
  65. $channel->save();
  66. $channel->delete();
  67. // Groups grant access by type, so retain the grant while another row of that type remains.
  68. $remaining = RechargeChannel::query()->where('data_type', $channel->data_type)
  69. ->where('type', $channel->type)->lockForUpdate()->first(['id']);
  70. $groupChanges = [];
  71. if (!$remaining) {
  72. $field = (int)$channel->data_type === 1 ? 'recharge_type' : 'withdraw_type';
  73. foreach (RechargeChannelGroup::query()->orderBy('id')->lockForUpdate()->get() as $group) {
  74. $types = (array)$group->{$field};
  75. $updated = array_values(array_filter($types, fn ($type) => trim((string)$type) !== (string)$channel->type));
  76. if ($types === $updated) continue;
  77. $groupChanges[] = ['id' => (int)$group->id, 'field' => $field, 'before' => $types, 'after' => $updated];
  78. $group->{$field} = $updated;
  79. $group->save();
  80. }
  81. }
  82. OperationAuditService::record('recharge_channel', $id, 'delete', $before,
  83. ['channel' => $channel->toArray(), 'group_changes' => $groupChanges]);
  84. }, 3);
  85. }
  86. private function identityLockReason(RechargeChannel $channel, bool $lock = false): string
  87. {
  88. foreach (['payment_gateways', 'payment_collection_channels', 'payment_direct_recharges'] as $table) {
  89. if (!Schema::hasTable($table) || !Schema::hasColumn($table, 'recharge_channel_id')) continue;
  90. $query = DB::table($table)->where('recharge_channel_id', $channel->id)->whereNull('deleted_at');
  91. if ($lock) $query->lockForUpdate();
  92. if ($query->first(['id'])) return '该通道已被支付配置引用,请先解除关联或新增通道,再更换公司或类型';
  93. }
  94. return '';
  95. }
  96. }