TransferApp.php 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. <?php
  2. namespace App\Module\Transfer\Models;
  3. use App\Module\Transfer\Casts\TransferAppCast;
  4. use UCore\ModelCore;
  5. /**
  6. * 划转应用配置模型
  7. *
  8. * field start
  9. * @property int $id 主键ID
  10. * @property string $keyname 应用标识符
  11. * @property string $title 应用显示名称
  12. * @property string $description 应用描述信息
  13. * @property int $out_id2 外部应用ID2-开放接口
  14. * @property int $out_id3 外部应用ID3-三方平台ID
  15. * @property int $currency_id 货币类型ID
  16. * @property int $fund_id 资金账户类型ID
  17. * @property int $fund_to_uid 转入目标账户UID
  18. * @property int $fund_in_uid 转入来源账户UID
  19. * @property float $exchange_rate 汇率(钱包:业务)
  20. * @property string $order_callback_url 结果通知API地址(为空则不通知)
  21. * @property string $order_in_info_url 转入查询API地址(为空则不查询)
  22. * @property string $order_out_create_url 转出创建API地址(为空则不创建)
  23. * @property string $order_out_info_url 转出查询API地址(为空则不查询)
  24. * @property bool $is_enabled 是否启用(1=启用,0=禁用)
  25. * @property bool $allow_transfer_in 是否允许转入(1=允许,0=禁止)
  26. * @property bool $allow_transfer_out 是否允许转出(1=允许,0=禁止)
  27. * @property \Carbon\Carbon $created_at 创建时间
  28. * @property \Carbon\Carbon $updated_at 更新时间
  29. * @property \Carbon\Carbon $deleted_at 删除时间
  30. * @property float $fee_in_rate 转入手续费率(0.0000-1.0000)
  31. * @property float $fee_out_rate 转出手续费率(0.0000-1.0000)
  32. * @property float $fee_in_min 转入最低手续费
  33. * @property float $fee_in_max 转入最高手续费(0为不限制)
  34. * @property float $fee_out_min 转出最低手续费
  35. * @property float $fee_out_max 转出最高手续费(0为不限制)
  36. * @property int $fee_account_uid 手续费收取账户UID
  37. * field end
  38. */
  39. class TransferApp extends ModelCore
  40. {
  41. /**
  42. * 数据表名
  43. */
  44. protected $table = 'transfer_apps';
  45. // attrlist start
  46. protected $fillable = [
  47. 'id',
  48. 'keyname',
  49. 'title',
  50. 'description',
  51. 'out_id2',
  52. 'out_id3',
  53. 'currency_id',
  54. 'fund_id',
  55. 'fund_to_uid',
  56. 'fund_in_uid',
  57. 'exchange_rate',
  58. 'order_callback_url',
  59. 'order_in_info_url',
  60. 'order_out_create_url',
  61. 'order_out_info_url',
  62. 'is_enabled',
  63. 'allow_transfer_in',
  64. 'allow_transfer_out',
  65. 'fee_in_rate',
  66. 'fee_out_rate',
  67. 'fee_in_min',
  68. 'fee_in_max',
  69. 'fee_out_min',
  70. 'fee_out_max',
  71. 'fee_account_uid',
  72. ];
  73. // attrlist end
  74. /**
  75. * 属性类型转换
  76. */
  77. protected $casts = [
  78. 'id' => 'integer',
  79. 'out_id2' => 'integer',
  80. 'out_id3' => 'integer',
  81. 'currency_id' => 'integer',
  82. 'fund_id' => 'integer',
  83. 'fund_to_uid' => 'integer',
  84. 'fund_in_uid' => 'integer',
  85. 'exchange_rate' => 'decimal:4',
  86. 'fee_in_rate' => 'decimal:4',
  87. 'fee_out_rate' => 'decimal:4',
  88. 'fee_in_min' => 'decimal:4',
  89. 'fee_in_max' => 'decimal:4',
  90. 'fee_out_min' => 'decimal:4',
  91. 'fee_out_max' => 'decimal:4',
  92. 'fee_account_uid' => 'integer',
  93. 'is_enabled' => 'boolean',
  94. 'allow_transfer_in' => 'boolean',
  95. 'allow_transfer_out' => 'boolean',
  96. 'created_at' => 'datetime',
  97. 'updated_at' => 'datetime',
  98. 'deleted_at' => 'datetime',
  99. ];
  100. /**
  101. * 软删除
  102. */
  103. protected $dates = ['deleted_at'];
  104. /**
  105. * 隐藏字段
  106. */
  107. protected $hidden = [];
  108. /**
  109. * 关联划转订单
  110. */
  111. public function orders()
  112. {
  113. return $this->hasMany(TransferOrder::class, 'transfer_app_id');
  114. }
  115. /**
  116. * 获取启用状态文本
  117. */
  118. public function getEnabledTextAttribute(): string
  119. {
  120. return $this->is_enabled ? '启用' : '禁用';
  121. }
  122. /**
  123. * 获取启用状态颜色
  124. */
  125. public function getEnabledColorAttribute(): string
  126. {
  127. return $this->is_enabled ? 'success' : 'danger';
  128. }
  129. /**
  130. * 获取允许转入状态文本
  131. */
  132. public function getAllowTransferInTextAttribute(): string
  133. {
  134. return $this->allow_transfer_in ? '允许' : '禁止';
  135. }
  136. /**
  137. * 获取允许转入状态颜色
  138. */
  139. public function getAllowTransferInColorAttribute(): string
  140. {
  141. return $this->allow_transfer_in ? 'success' : 'danger';
  142. }
  143. /**
  144. * 获取允许转出状态文本
  145. */
  146. public function getAllowTransferOutTextAttribute(): string
  147. {
  148. return $this->allow_transfer_out ? '允许' : '禁止';
  149. }
  150. /**
  151. * 获取允许转出状态颜色
  152. */
  153. public function getAllowTransferOutColorAttribute(): string
  154. {
  155. return $this->allow_transfer_out ? 'success' : 'danger';
  156. }
  157. /**
  158. * 判断是否支持转入
  159. * 支持转入的条件:应用启用 && 允许转入 && (配置了转入查询URL 或者 没有配置任何外部API(本地处理))
  160. */
  161. public function supportsTransferIn(): bool
  162. {
  163. if (!$this->is_enabled || !$this->allow_transfer_in) {
  164. return false;
  165. }
  166. return !empty($this->order_in_info_url) ||
  167. (empty($this->order_callback_url) &&
  168. empty($this->order_in_info_url) &&
  169. empty($this->order_out_create_url) &&
  170. empty($this->order_out_info_url));
  171. }
  172. /**
  173. * 判断是否支持转出
  174. * 支持转出的条件:应用启用 && 允许转出 && (配置了转出创建URL 或者 没有配置任何外部API(本地处理))
  175. */
  176. public function supportsTransferOut(): bool
  177. {
  178. if (!$this->is_enabled || !$this->allow_transfer_out) {
  179. return false;
  180. }
  181. return !empty($this->order_out_create_url) ||
  182. (empty($this->order_callback_url) &&
  183. empty($this->order_in_info_url) &&
  184. empty($this->order_out_create_url) &&
  185. empty($this->order_out_info_url));
  186. }
  187. /**
  188. * 判断是否支持回调
  189. */
  190. public function supportsCallback(): bool
  191. {
  192. return !empty($this->order_callback_url);
  193. }
  194. /**
  195. * 获取手续费收取账户UID
  196. *
  197. * @return int 手续费收取账户UID,默认为1
  198. */
  199. public function getFeeAccountUid(): int
  200. {
  201. return $this->fee_account_uid > 0 ? $this->fee_account_uid : 1;
  202. }
  203. /**
  204. * 获取手续费收取账户信息
  205. */
  206. public function getFeeAccount()
  207. {
  208. $feeAccountUid = $this->getFeeAccountUid();
  209. return \App\Module\Fund\Services\FundService::getAccountInfo($feeAccountUid);
  210. }
  211. /**
  212. * 计算转入手续费
  213. *
  214. * @param string $amount 转入金额
  215. * @return array ['fee_rate' => 手续费率, 'fee_amount' => 手续费金额, 'actual_amount' => 实际到账金额]
  216. * @deprecated 模型内不应该有逻辑
  217. */
  218. public function calculateInFee(string $amount): array
  219. {
  220. return $this->calculateFee($amount, $this->fee_in_rate, $this->fee_in_min, $this->fee_in_max);
  221. }
  222. /**
  223. * 计算转出手续费
  224. *
  225. * @param string $amount 转出金额
  226. * @param array $context 额外的上下文数据(包含用户ID等信息)
  227. * @return array ['fee_rate' => 手续费率, 'fee_amount' => 手续费金额, 'actual_amount' => 用户总支付金额]
  228. * @deprecated 模型内,不应该有逻辑
  229. */
  230. public function calculateOutFee(string $amount, array $context = []): array
  231. {
  232. // 使用FeeService来计算手续费,这样可以触发事件机制
  233. return \App\Module\Transfer\Services\FeeService::calculateOutFee($this, $amount, $context);
  234. }
  235. /**
  236. * 计算手续费的通用方法(从金额中扣除手续费)
  237. *
  238. * @param string $amount 金额
  239. * @param float $feeRate 手续费率
  240. * @param float $minFee 最低手续费
  241. * @param float $maxFee 最高手续费
  242. * @return array
  243. */
  244. private function calculateFee(string $amount, float $feeRate, float $minFee, float $maxFee): array
  245. {
  246. $amountDecimal = bcmul($amount, '1', 4); // 转换为4位小数
  247. // 按比例计算手续费
  248. $feeAmount = bcmul($amountDecimal, (string)$feeRate, 4);
  249. // 应用最低手续费限制
  250. if (bccomp($feeAmount, (string)$minFee, 4) < 0) {
  251. $feeAmount = bcmul((string)$minFee, '1', 4);
  252. }
  253. // 应用最高手续费限制(如果设置了)
  254. if ($maxFee > 0 && bccomp($feeAmount, (string)$maxFee, 4) > 0) {
  255. $feeAmount = bcmul((string)$maxFee, '1', 4);
  256. }
  257. // 计算实际到账金额(扣除手续费后)
  258. $actualAmount = bcsub($amountDecimal, $feeAmount, 4);
  259. return [
  260. 'fee_rate' => $feeRate,
  261. 'fee_amount' => $feeAmount,
  262. 'actual_amount' => $actualAmount, // 对于转入:用户实际收到金额
  263. ];
  264. }
  265. /**
  266. * 检查是否启用了手续费
  267. *
  268. * @param string $type 类型:'in' 或 'out'
  269. * @return bool
  270. */
  271. public function isFeeEnabled(string $type): bool
  272. {
  273. if ($type === 'in') {
  274. return $this->fee_in_rate > 0 || $this->fee_in_min > 0;
  275. } elseif ($type === 'out') {
  276. return $this->fee_out_rate > 0 || $this->fee_out_min > 0;
  277. }
  278. return false;
  279. }
  280. }