支付宝记账本
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

221 lines
5.7 KiB

5 months ago
5 months ago
5 months ago
  1. <?php
  2. namespace App\Services;
  3. use App\Libs\AppException;
  4. use App\Models\MchApp;
  5. class OutService
  6. {
  7. /**
  8. * 生成签名
  9. * @param array $params
  10. * @param string $secretKey
  11. * @return string
  12. */
  13. public function makeSign(array $params, string $secretKey): string
  14. {
  15. ksort($params);
  16. $signStr = '';
  17. foreach ($params as $key => $value) {
  18. if ($value === null || $value === '' || $key === 'sign') {
  19. continue;
  20. }
  21. if ($signStr) {
  22. $signStr .= '&';
  23. }
  24. $signStr .= $key.'='.$value;
  25. }
  26. $signStr .= '&key='.$secretKey;
  27. $sign = md5($signStr);
  28. return strtoupper($sign);
  29. }
  30. /**
  31. * 时间戳偏差校验
  32. * @throws AppException
  33. */
  34. private function checkRequestTime(int $requestTime): void
  35. {
  36. if (abs(time() - $requestTime / 1000) > 300) {
  37. throw new AppException('时间戳相差大于300秒');
  38. }
  39. }
  40. /**
  41. * 渠道编码校验
  42. * @throws AppException
  43. */
  44. private function checkIfCode(string $ifCode): void
  45. {
  46. if ($ifCode !== 'alipay') {
  47. throw new AppException('渠道编码固定为:alipay');
  48. }
  49. }
  50. /**
  51. * 接口版本号校验
  52. * @throws AppException
  53. */
  54. private function checkVersion(string $version): void
  55. {
  56. if ($version !== '1.0') {
  57. throw new AppException('接口版本号固定为:1.0');
  58. }
  59. }
  60. /**
  61. * 签名类型校验
  62. * @throws AppException
  63. */
  64. private function checkSignType(string $signType): void
  65. {
  66. if ($signType !== 'MD5') {
  67. throw new AppException('签名类型固定为:MD5');
  68. }
  69. }
  70. /**
  71. * 校验签名
  72. * @throws AppException
  73. */
  74. private function checkSign(array $params, string $secretKey): void
  75. {
  76. if (empty($params['sign'])) {
  77. throw new AppException('签名缺失');
  78. }
  79. $checkSign = $this->makeSign($params, $secretKey);
  80. if ($checkSign !== $params['sign']) {
  81. throw new AppException('签名不匹配');
  82. }
  83. }
  84. /**
  85. * 根据商户号和应用ID获取应用
  86. * @throws AppException
  87. */
  88. private function getMchApp(string $mchNo, string $appId): MchApp
  89. {
  90. $app = MchApp::query()
  91. ->where('mch_no', $mchNo)
  92. ->where('app_id', $appId)
  93. ->first();
  94. if (empty($app)) {
  95. throw new AppException('应用不存在');
  96. }
  97. if (empty($app->account_book_id)) {
  98. throw new AppException('还未开通记账本');
  99. }
  100. return $app;
  101. }
  102. /**
  103. * 查询记账本余额
  104. * @param array $params
  105. * @return array
  106. * @throws AppException
  107. */
  108. public function queryBalance(array $params): array
  109. {
  110. $this->checkRequestTime($params['reqTime']);
  111. $this->checkIfCode($params['ifCode']);
  112. $this->checkVersion($params['version']);
  113. $this->checkSignType($params['signType']);
  114. $mchApp = $this->getMchApp($params['mchNo'], $params['appId']);
  115. $this->checkSign($params, $mchApp->secret_key);
  116. // TODO mock
  117. $data = [
  118. 'availableAmount' => 700,
  119. ];
  120. $sign = $this->makeSign($data, $mchApp->secret_key);
  121. return [$data, $sign];
  122. }
  123. /**
  124. * 单笔转账
  125. * @param array $params
  126. * @return array
  127. * @throws AppException
  128. */
  129. public function transferOrder(array $params): array
  130. {
  131. $this->checkRequestTime($params['reqTime']);
  132. $this->checkIfCode($params['ifCode']);
  133. $this->checkVersion($params['version']);
  134. $this->checkSignType($params['signType']);
  135. $mchApp = $this->getMchApp($params['mchNo'], $params['appId']);
  136. $this->checkSign($params, $mchApp->secret_key);
  137. // TODO mock
  138. $data = [
  139. 'accountName' => '广西拉米信息科技有限公司',
  140. 'accountNo' => '3858074971@qq.com',
  141. 'amount' => 300,
  142. 'channelOrderNo' => '20250825020070011530820074021763',
  143. 'mchOrderNo' => 'P00000000000000000010',
  144. 'state' => 1,
  145. 'transferId' => 'T1708316875559008'
  146. ];
  147. $sign = $this->makeSign($data, $mchApp->secret_key);
  148. return [$data, $sign];
  149. }
  150. /**
  151. * 转账查询
  152. * @param array $params
  153. * @return array
  154. * @throws AppException
  155. */
  156. public function transferQuery(array $params): array
  157. {
  158. $this->checkRequestTime($params['reqTime']);
  159. $this->checkVersion($params['version']);
  160. $this->checkSignType($params['signType']);
  161. $mchApp = $this->getMchApp($params['mchNo'], $params['appId']);
  162. $this->checkSign($params, $mchApp->secret_key);
  163. // TODO mock
  164. $data = [
  165. 'accountName' => '广西拉米信息科技有限公司',
  166. 'accountNo' => '3858074971@qq.com',
  167. 'amount' => 300,
  168. 'appId' => '664714f9e4b078d6ee5d0007',
  169. 'channelOrderNo' => '20250825020070011530820074021763',
  170. 'createdAt' => 1756092887724,
  171. 'currency' => 'cny',
  172. 'entryType' => 'ALIPAY_CASH',
  173. 'ifCode' => 'alipay',
  174. 'mchNo' => 'M1715934457',
  175. 'mchOrderNo' => 'P00000000000000000010',
  176. 'state' => 2,
  177. 'successTime' => 1756092888000,
  178. 'transferDesc' => '提现到账3.00元',
  179. 'transferId' => 'T1708316875559008'
  180. ];
  181. $sign = $this->makeSign($data, $mchApp->secret_key);
  182. return [$data, $sign];
  183. }
  184. }