CurrencyExchange.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. <?php
  2. namespace App\Utils;
  3. use Cache;
  4. use Exception;
  5. use Http;
  6. use Illuminate\Http\Client\PendingRequest;
  7. use Log;
  8. class CurrencyExchange
  9. {
  10. private static PendingRequest $basicRequest;
  11. /**
  12. * @param string $target target Currency
  13. * @param float|int $amount exchange amount
  14. * @param string|null $base Base Currency
  15. * @return float|null amount in target currency
  16. */
  17. public static function convert(string $target, float|int $amount, string $base = null): ?float
  18. {
  19. if ($base === null) {
  20. $base = (string) sysConfig('standard_currency');
  21. }
  22. $cacheKey = "Currency_{$base}_{$target}_ExRate";
  23. if (Cache::has($cacheKey)) {
  24. return round($amount * Cache::get($cacheKey), 2);
  25. }
  26. $apis = ['exchangerateApi', 'k780', 'it120', 'exchangerate', 'fixer', 'currencyData', 'exchangeRatesData', 'jsdelivrFile'];
  27. self::$basicRequest = Http::timeout(15)->withOptions(['http_errors' => false])->withUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36');
  28. foreach ($apis as $api) {
  29. try {
  30. $rate = self::callApis($api, $base, $target);
  31. if ($rate !== null) {
  32. Cache::put($cacheKey, $rate, Day);
  33. return round($amount * $rate, 2);
  34. }
  35. } catch (Exception $e) {
  36. Log::error("[$api] ±ÒÖÖ»ãÂÊÐÅÏ¢»ñÈ¡±¨´í: ".$e->getMessage());
  37. continue;
  38. }
  39. }
  40. return null;
  41. }
  42. private static function callApis(string $api, string $base, string $target): ?float
  43. {
  44. return match ($api) {
  45. 'exchangerateApi' => self::exchangerateApi($base, $target),
  46. 'k780' => self::k780($base, $target),
  47. 'it120' => self::it120($base, $target),
  48. 'exchangerate' => self::exchangerate($base, $target),
  49. 'fixer' => self::fixer($base, $target),
  50. 'currencyData' => self::currencyData($base, $target),
  51. 'exchangeRatesData' => self::exchangeRatesData($base, $target),
  52. 'jsdelivrFile' => self::jsdelivrFile($base, $target),
  53. };
  54. }
  55. private static function exchangerateApi(string $base, string $target): ?float
  56. { // Reference: https://www.exchangerate-api.com/docs/php-currency-api
  57. $key = config('services.currency.exchangerate-api_key');
  58. if ($key) {
  59. $url = "https://v6.exchangerate-api.com/v6/$key/pair/$base/$target";
  60. } else {
  61. $url = "https://open.er-api.com/v6/latest/$base";
  62. }
  63. $response = self::$basicRequest->get($url);
  64. if ($response->ok()) {
  65. $data = $response->json();
  66. if ($data['result'] === 'success') {
  67. return $data[$key ? 'conversion_rate' : 'rates'][$target];
  68. }
  69. Log::emergency('[CurrencyExchange]exchangerateApi exchange failed with following message: '.$data['error-type']);
  70. } else {
  71. Log::emergency('[CurrencyExchange]exchangerateApi request failed '.var_export($response, true));
  72. }
  73. return null;
  74. }
  75. private static function k780(string $base, string $target): ?float
  76. { // Reference: https://www.nowapi.com/api/finance.rate
  77. $response = self::$basicRequest->get("https://sapi.k780.com/?app=finance.rate&scur=$base&tcur=$target&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json");
  78. if ($response->ok()) {
  79. $data = $response->json();
  80. if ($data['success'] === '1') {
  81. return $data['result']['rate'];
  82. }
  83. Log::emergency('[CurrencyExchange]Nowapi exchange failed with following message: '.$data['msg']);
  84. } else {
  85. Log::emergency('[CurrencyExchange]Nowapi request failed'.var_export($response, true));
  86. }
  87. return null;
  88. }
  89. private static function it120(string $base, string $target): ?float
  90. { // Reference: https://www.it120.cc/help/fnun8g.html
  91. $response = self::$basicRequest->get("https://api.it120.cc/gooking/forex/rate?fromCode=$target&toCode=$base");
  92. if ($response->ok()) {
  93. $data = $response->json();
  94. if ($data['code'] === 0) {
  95. return $data['data']['rate'];
  96. }
  97. Log::emergency('[CurrencyExchange]it120 exchange failed with following message: '.$data['msg']);
  98. } else {
  99. Log::emergency('[CurrencyExchange]it120 request failed'.var_export($response, true));
  100. }
  101. return null;
  102. }
  103. private static function exchangerate(string $base, string $target): ?float
  104. { // Reference: https://exchangerate.host/#/
  105. $response = self::$basicRequest->get("https://api.exchangerate.host/latest?base=$base&symbols=$target");
  106. if ($response->ok()) {
  107. $data = $response->json();
  108. if ($data['success'] && $data['base'] === $base) {
  109. return $data['rates'][$target];
  110. }
  111. Log::emergency('[CurrencyExchange]exchangerate exchange failed with following message: '.$data['error-type']);
  112. }
  113. Log::emergency('[CurrencyExchange]exchangerate request failed');
  114. return null;
  115. }
  116. private static function fixer(string $base, string $target): ?float
  117. { // Reference: https://apilayer.com/marketplace/fixer-api RATE LIMIT: 100 Requests / Monthly!!!!
  118. $key = config('services.currency.apiLayer_key');
  119. if ($key) {
  120. $response = self::$basicRequest->withHeaders(['apikey' => $key])->get("https://api.apilayer.com/fixer/latest?symbols=$target&base=$base");
  121. if ($response->ok()) {
  122. $data = $response->json();
  123. if ($data['success']) {
  124. return $data['rates'][$target];
  125. }
  126. Log::emergency('[CurrencyExchange]Fixer exchange failed with following message: '.$data['error']['type'] ?? '');
  127. } else {
  128. Log::emergency('[CurrencyExchange]Fixer request failed'.var_export($response, true));
  129. }
  130. }
  131. return null;
  132. }
  133. private static function currencyData(string $base, string $target): ?float
  134. { // Reference: https://apilayer.com/marketplace/currency_data-api RATE LIMIT: 100 Requests / Monthly
  135. $key = config('services.currency.apiLayer_key');
  136. if ($key) {
  137. $response = self::$basicRequest->withHeaders(['apikey' => $key])->get("https://api.apilayer.com/currency_data/live?source=$base&currencies=$target");
  138. if ($response->ok()) {
  139. $data = $response->json();
  140. if ($data['success']) {
  141. return $data['quotes'][$base.$target];
  142. }
  143. Log::emergency('[CurrencyExchange]Currency Data exchange failed with following message: '.$data['error']['info'] ?? '');
  144. } else {
  145. Log::emergency('[CurrencyExchange]Currency Data request failed'.var_export($response, true));
  146. }
  147. }
  148. return null;
  149. }
  150. private static function exchangeRatesData(string $base, string $target): ?float
  151. { // Reference: https://apilayer.com/marketplace/exchangerates_data-api RATE LIMIT: 250 Requests / Monthly
  152. $key = config('services.currency.apiLayer_key');
  153. if ($key) {
  154. $response = self::$basicRequest->withHeaders(['apikey' => $key])->get("https://api.apilayer.com/exchangerates_data/latest?symbols=$target&base=$base");
  155. if ($response->ok()) {
  156. $data = $response->json();
  157. if ($data['success']) {
  158. return $data['rates'][$target];
  159. }
  160. Log::emergency('[CurrencyExchange]Exchange Rates Data exchange failed with following message: '.$data['error']['message'] ?? '');
  161. } else {
  162. Log::emergency('[CurrencyExchange]Exchange Rates Data request failed'.var_export($response, true));
  163. }
  164. }
  165. return null;
  166. }
  167. private static function jsdelivrFile(string $base, string $target): ?float
  168. { // Reference: https://github.com/fawazahmed0/currency-api
  169. $response = self::$basicRequest->get('https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api@1/latest/currencies/'.strtolower($base).'/'.strtolower($target).'.min.json');
  170. if ($response->ok()) {
  171. $data = $response->json();
  172. return $data[strtolower($target)];
  173. }
  174. return null;
  175. }
  176. }