EffectiveBet.php 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. <?php
  2. namespace App\Services\Sharing;
  3. use Carbon\Carbon;
  4. use InvalidArgumentException;
  5. /** Shared arithmetic only: callers retain their own eligibility and accounting dates. */
  6. final class EffectiveBet
  7. {
  8. public const SCALE = 10;
  9. public static function amount($betAmount, $netProfit, bool $settled = true, bool $voided = false): string
  10. {
  11. if (!$settled || $voided) return self::zero();
  12. $bet = self::decimal($betAmount);
  13. $profit = self::decimal($netProfit);
  14. if (bccomp($bet, '0', self::SCALE) < 0) {
  15. throw new InvalidArgumentException('Bet amount cannot be negative');
  16. }
  17. $absolute = bccomp($profit, '0', self::SCALE) < 0
  18. ? bcsub('0', $profit, self::SCALE) : $profit;
  19. return bccomp($bet, $absolute, self::SCALE) <= 0 ? $bet : $absolute;
  20. }
  21. public static function decimal($value): string
  22. {
  23. $value = trim((string) $value);
  24. if (!preg_match('/^-?\d+(?:\.\d+)?$/D', $value)) {
  25. throw new InvalidArgumentException('Expected a plain decimal amount');
  26. }
  27. return bcadd($value, '0', self::SCALE);
  28. }
  29. public static function zero(): string
  30. {
  31. return '0.0000000000';
  32. }
  33. public static function newYorkDate($sourceTime, string $sourceTimezone = 'Asia/Shanghai'): string
  34. {
  35. return self::time($sourceTime, $sourceTimezone)->setTimezone('America/New_York')->toDateString();
  36. }
  37. /** Reject missing dates rather than Carbon's implicit current-time fallback. */
  38. public static function time($sourceTime, string $sourceTimezone = 'Asia/Shanghai'): Carbon
  39. {
  40. if ($sourceTime instanceof \DateTimeInterface) return Carbon::instance($sourceTime);
  41. if ($sourceTime === null || trim((string) $sourceTime) === '') {
  42. throw new InvalidArgumentException('Source time is missing');
  43. }
  44. if (is_numeric($sourceTime)) {
  45. if (!preg_match('/^\d{9,11}$/D', (string) $sourceTime)) {
  46. throw new InvalidArgumentException('Invalid source timestamp');
  47. }
  48. return Carbon::createFromTimestamp((int) $sourceTime, 'UTC');
  49. }
  50. if (!preg_match('/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?$/D', (string) $sourceTime)) {
  51. throw new InvalidArgumentException('Invalid source datetime');
  52. }
  53. $parsed = Carbon::parse($sourceTime, $sourceTimezone);
  54. if ($parsed->format('Y-m-d H:i:s') !== str_replace('T', ' ', substr((string) $sourceTime, 0, 19))) {
  55. throw new InvalidArgumentException('Source datetime is not a valid calendar date');
  56. }
  57. return $parsed;
  58. }
  59. }