CurrencyExchange.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. <?php
  2. namespace App\Components;
  3. use Cache;
  4. use Http;
  5. use Log;
  6. class CurrencyExchange
  7. {
  8. /**
  9. * @param string $target target Currency
  10. * @param int|float|false $amount exchange amount
  11. * @param string $base Base Currency
  12. * @return false|float amount in target currency
  13. */
  14. public static function convert(string $target, $amount = false, string $base = 'default')
  15. {
  16. if ($base === 'default') {
  17. $base = sysConfig('standard_currency', 'CNY');
  18. }
  19. $cacheKey = "Currency_{$base}_{$target}_ExRate";
  20. $isStored = Cache::has($cacheKey);
  21. $rate = $isStored ? Cache::get($cacheKey) : false;
  22. $case = 0;
  23. while ($case < 7 && $rate === false) {
  24. switch ($case) {
  25. case 0:
  26. $rate = self::exchangerateApi($base, $target);
  27. break;
  28. case 1:
  29. $rate = self::k780($base, $target);
  30. break;
  31. case 2:
  32. $rate = self::it120($base, $target);
  33. break;
  34. case 3:
  35. $rate = self::exchangerate($base, $target);
  36. break;
  37. case 4:
  38. $rate = self::fixer($base, $target);
  39. break;
  40. case 5:
  41. $rate = self::currencyData($base, $target);
  42. break;
  43. case 6:
  44. $rate = self::exchangeRatesData($base, $target);
  45. break;
  46. default:
  47. break;
  48. }
  49. $case++;
  50. }
  51. if ($rate !== false) {
  52. if (! $isStored) {
  53. Cache::put($cacheKey, $rate, Day);
  54. }
  55. if ($amount === false) {
  56. return $rate;
  57. }
  58. return round($amount * $rate, 2);
  59. }
  60. return false;
  61. }
  62. private static function exchangerateApi($base, $target)
  63. { // Reference: https://www.exchangerate-api.com/docs/php-currency-api
  64. $response = Http::retry(3)->get('https://open.er-api.com/v6/latest/'.$base); // https://v6.exchangerate-api.com/v6/{{your token}}/latest/USD
  65. if ($response->ok()) {
  66. $response = $response->json();
  67. if ($response['result'] === 'success') {
  68. return $response['rates'][$target];
  69. }
  70. Log::emergency('[CurrencyExchange]exchangerateApi exchange failed with following message: '.$response['error-type']);
  71. } else {
  72. Log::emergency('[CurrencyExchange]exchangerateApi request failed '.var_export($response, true));
  73. }
  74. return false;
  75. }
  76. private static function k780($base, $target)
  77. { // Reference: https://www.nowapi.com/api/finance.rate
  78. $response = Http::retry(3)->get("https://sapi.k780.com/?app=finance.rate&scur={$base}&tcur={$target}&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json");
  79. if ($response->ok()) {
  80. $response = $response->json();
  81. if ($response['success'] === '1') {
  82. return $response['result']['rate'];
  83. }
  84. Log::emergency('[CurrencyExchange]Nowapi exchange failed with following message: '.$response['msg']);
  85. } else {
  86. Log::emergency('[CurrencyExchange]Nowapi request failed'.var_export($response, true));
  87. }
  88. return false;
  89. }
  90. private static function it120($base, $target)
  91. { // Reference: https://www.it120.cc/help/fnun8g.html
  92. $response = Http::retry(3)->get("https://api.it120.cc/gooking/forex/rate?fromCode={$target}&toCode={$base}");
  93. if ($response->ok()) {
  94. $response = $response->json();
  95. if ($response['code'] === 0) {
  96. return $response['data']['rate'];
  97. }
  98. Log::emergency('[CurrencyExchange]it120 exchange failed with following message: '.$response['msg']);
  99. } else {
  100. Log::emergency('[CurrencyExchange]it120 request failed'.var_export($response, true));
  101. }
  102. return false;
  103. }
  104. private static function exchangerate($base, $target)
  105. { // Reference: https://exchangerate.host/#/
  106. $response = file_get_contents("https://api.exchangerate.host/latest?symbols={$target}&base={$base}");
  107. if (false !== $response) {
  108. $response = json_decode($response, true);
  109. if ($response['success'] === true) {
  110. if ($response['base'] === $base) {
  111. return $response['rates'][$target];
  112. }
  113. Log::emergency('[CurrencyExchange]exchangerate exchange failed with following message: Get un-supported base currency '.$response['base']);
  114. } else {
  115. Log::emergency('[CurrencyExchange]exchangerate exchange failed with following message: '.$response['error-type']);
  116. }
  117. } else {
  118. Log::emergency('[CurrencyExchange]exchangerate request failed');
  119. }
  120. return false;
  121. }
  122. private static function fixer($base, $target)
  123. { // Reference: https://apilayer.com/marketplace/fixer-api
  124. // RATE LIMIT: 100 Requests / Monthly
  125. if (! config('services.apiLayer')) {
  126. return false;
  127. }
  128. $response = Http::retry(3)->withHeaders(['apikey' => config('services.apiLayer')])->get("https://api.apilayer.com/fixer/latest?symbols={$target}&base={$base}");
  129. if ($response->ok()) {
  130. $response = $response->json();
  131. if ($response['success'] === true) {
  132. return $response['rates'][$target];
  133. }
  134. Log::emergency('[CurrencyExchange]Fixer exchange failed with following message: '.$response['error']['type'] ?? '');
  135. } else {
  136. Log::emergency('[CurrencyExchange]Fixer request failed'.var_export($response, true));
  137. }
  138. return false;
  139. }
  140. private static function currencyData($base, $target)
  141. { // Reference: https://apilayer.com/marketplace/currency_data-api
  142. // RATE LIMIT: 100 Requests / Monthly
  143. if (! config('services.apiLayer')) {
  144. return false;
  145. }
  146. $response = Http::retry(3)->withHeaders(['apikey' => config('services.apiLayer')])
  147. ->get("https://api.apilayer.com/currency_data/live?source={$base}&currencies={$target}");
  148. if ($response->ok()) {
  149. $response = $response->json();
  150. if ($response['success'] === true) {
  151. return $response['quotes'][$base.$target];
  152. }
  153. Log::emergency('[CurrencyExchange]Currency Data exchange failed with following message: '.$response['error']['info'] ?? '');
  154. } else {
  155. Log::emergency('[CurrencyExchange]Currency Data request failed'.var_export($response, true));
  156. }
  157. return false;
  158. }
  159. private static function exchangeRatesData($base, $target)
  160. { // Reference: https://apilayer.com/marketplace/exchangerates_data-api
  161. // RATE LIMIT: 250 Requests / Monthly
  162. if (! config('services.apiLayer')) {
  163. return false;
  164. }
  165. $response = Http::retry(3)->withHeaders(['apikey' => config('services.apiLayer')])
  166. ->get("https://api.apilayer.com/exchangerates_data/latest?symbols={$target}&base={$base}");
  167. if ($response->ok()) {
  168. $response = $response->json();
  169. if ($response['success'] === true) {
  170. return $response['rates'][$target];
  171. }
  172. Log::emergency('[CurrencyExchange]Exchange Rates Data exchange failed with following message: '.$response['error']['message'] ?? '');
  173. } else {
  174. Log::emergency('[CurrencyExchange]Exchange Rates Data request failed'.var_export($response, true));
  175. }
  176. return false;
  177. }
  178. }