PaymentTemplate.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | 萤火商城系统 [ 致力于通过产品和服务,帮助商家高效化开拓市场 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2017~2025 https://www.yiovo.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed 这不是一个自由软件,不允许对程序代码以任何形式任何目的的再发行
  8. // +----------------------------------------------------------------------
  9. // | Author: 萤火科技 <admin@yiovo.com>
  10. // +----------------------------------------------------------------------
  11. declare (strict_types=1);
  12. namespace app\store\model;
  13. use think\facade\Filesystem;
  14. use cores\exception\BaseException;
  15. use app\common\library\helper;
  16. use app\store\model\Payment as PaymentModel;
  17. use app\common\enum\payment\Method as PaymentMethodEnum;
  18. use app\common\model\PaymentTemplate as PaymentTemplateModel;
  19. use app\common\library\wechat\payment\PlatformCertDown;
  20. /**
  21. * 模型类:支付方式记录
  22. * Class PaymentTemplate
  23. * @package app\store\model
  24. */
  25. class PaymentTemplate extends PaymentTemplateModel
  26. {
  27. /**
  28. * 获取列表
  29. * @return \think\Paginator
  30. * @throws \think\db\exception\DbException
  31. */
  32. public function getList(): \think\Paginator
  33. {
  34. return $this->where('is_delete', '=', 0)
  35. ->order(['sort' => 'asc', $this->getPk()])
  36. ->paginate(15);
  37. }
  38. /**
  39. * 新增记录
  40. * @param array $data
  41. * @return bool|false
  42. * @throws BaseException
  43. */
  44. public function add(array $data): bool
  45. {
  46. // 整理保存的数据
  47. $config = helper::jsonDecode(htmlspecialchars_decode($data['config']));
  48. $methodConfig = $config[$data['method']];
  49. $data['store_id'] = self::$storeId;
  50. // 写入证书文件
  51. $data['config'] = [$data['method'] => $this->certFileName($data['method'], $methodConfig)];
  52. // 记录微信支付v3平台证书序号或微信支付公钥ID
  53. $data['wechatpay_serial'] = $this->wechatpaySerial($data);
  54. // 写入到数据库
  55. return $this->save($data);
  56. }
  57. /**
  58. * 更新记录
  59. * @param array $data
  60. * @return bool
  61. * @throws BaseException
  62. */
  63. public function edit(array $data): bool
  64. {
  65. // 整理保存的数据
  66. $config = helper::jsonDecode(htmlspecialchars_decode($data['config']));
  67. $methodConfig = $config[$data['method']];
  68. $data['store_id'] = self::$storeId;
  69. // 记录证书文件名
  70. $data['config'] = [
  71. $data['method'] => array_merge($methodConfig, $this->certFileName($data['method'], $methodConfig))
  72. ];
  73. // 记录微信支付v3平台证书序号或微信支付公钥ID
  74. $data['wechatpay_serial'] = $this->wechatpaySerial($data);
  75. // 更新到数据库
  76. return $this->save($data) !== false;
  77. }
  78. /**
  79. * 记录微信支付v3平台证书序号或微信支付公钥ID
  80. * @param array $data
  81. * @return string
  82. */
  83. private function wechatpaySerial(array $data): string
  84. {
  85. if ($data['method'] === 'wechat' && $data['config']['wechat']['version'] === 'v3') {
  86. $mchType = $data['config']['wechat']['mchType'];
  87. $field1 = $mchType == 'normal' ? 'signatureMethod' : 'spSignatureMethod';
  88. $field2 = $mchType == 'normal' ? 'publicKeyId' : 'spPublicKeyId';
  89. return $data['config']['wechat'][$mchType][$field1] == 'publicKey'
  90. ? $data['config']['wechat'][$mchType][$field2]
  91. : $data['config']['wechat'][$mchType]['platformCertSerial'];
  92. }
  93. return '';
  94. }
  95. /**
  96. * 记录证书文件名
  97. * @param string $method 支付方式类型
  98. * @param array $config 支付配置
  99. * @return array
  100. * @throws BaseException
  101. */
  102. private function certFileName(string $method, array $config): array
  103. {
  104. if ($method === PaymentMethodEnum::WECHAT) {
  105. $config['normal'] = $this->saveCertFileNames($config['normal'], $method, [
  106. 'publicKey',
  107. 'apiclientCert',
  108. 'apiclientKey'
  109. ], 'pem');
  110. $config['provider'] = $this->saveCertFileNames($config['provider'], $method, [
  111. 'spPublicKey',
  112. 'spApiclientCert',
  113. 'spApiclientKey'
  114. ], 'pem');
  115. // 微信支付v3验签方式是平台证书时, 需自动下载平台证书
  116. if ($config['version'] === 'v3') {
  117. $mchType = $config['mchType'];
  118. $field1 = $mchType == 'normal' ? 'signatureMethod' : 'spSignatureMethod';
  119. $config[$mchType][$field1] == 'platformCert' && $this->downloadPlatformCert($config);
  120. }
  121. }
  122. if ($method === PaymentMethodEnum::ALIPAY && $config['signMode'] == 10) {
  123. $config = $this->saveCertFileNames($config, $method, [
  124. 'appCertPublicKey',
  125. 'alipayCertPublicKey',
  126. 'alipayRootCert'
  127. ], 'crt');
  128. }
  129. return $config;
  130. }
  131. /**
  132. * 微信支付v3下载 [微信平台证书]
  133. * @param array $config
  134. * @return void
  135. * @throws BaseException
  136. */
  137. private function downloadPlatformCert(array &$config): void
  138. {
  139. // 配置项的字段(用于区分普通商户和子商户)
  140. $field = $config['mchType'] === 'normal' ? ['normal', 'apiclientCert', 'apiclientKey', 'apiKey', 'mchId']
  141. : ['provider', 'spApiclientCert', 'spApiclientKey', 'spApiKey', 'spMchId'];
  142. // 微信商户API证书 [公钥]
  143. $publicKey = parent::realPathCertFile(PaymentMethodEnum::WECHAT, $config[$field[0]][$field[1]], self::$storeId);
  144. // 微信商户API证书 [私钥]
  145. $privatekey = parent::realPathCertFile(PaymentMethodEnum::WECHAT, $config[$field[0]][$field[2]], self::$storeId);
  146. // 生成平台证书文件名
  147. $platformCertName = $this->buildCertFileName('platformCert', 'pem');
  148. // 证书保存的目录
  149. $certFolder = parent::certFolder(PaymentMethodEnum::WECHAT, self::$storeId);
  150. // 下载平台证书
  151. $downloader = new PlatformCertDown([
  152. 'key' => $config[$field[0]][$field[3]],
  153. 'mchid' => $config[$field[0]][$field[4]],
  154. 'privatekey' => $privatekey,
  155. 'publicKey' => $publicKey,
  156. 'output' => $certFolder,
  157. 'fileName' => $platformCertName
  158. ]);
  159. $downloader->run();
  160. // 记录平台证书
  161. $config[$field[0]]['platformCert'] = $platformCertName;
  162. $config[$field[0]]['platformCertSerial'] = $downloader->getPlatformCertSerial();
  163. }
  164. /**
  165. * 批量设置证书文件名
  166. * @param array $config 配置项
  167. * @param string $method 支付方式类型
  168. * @param array $certNames
  169. * @param string $ext 文件后缀
  170. * @return array
  171. */
  172. private function saveCertFileNames(array $config, string $method, array $certNames, string $ext): array
  173. {
  174. foreach ($certNames as $name) {
  175. $config[$name] = $this->writeFile($method, $name, $ext) ?: $config[$name];
  176. }
  177. return $config;
  178. }
  179. /**
  180. * 写入证书文件
  181. * @param string $method 支付方式类型
  182. * @param string $name 证书名称
  183. * @param string $ext 文件后缀
  184. * @return string|false
  185. */
  186. private function writeFile(string $method, string $name, string $ext)
  187. {
  188. // 接收上传的文件
  189. $file = request()->file($name);
  190. if (empty($file)) {
  191. return false;
  192. }
  193. // 证书文件夹路径
  194. $dirPath = "payment/{$method}/" . self::$storeId;
  195. // 证书文件名
  196. $fileName = $this->buildCertFileName($name, $ext);
  197. // 写入到本地服务器
  198. Filesystem::disk('data')->putFileAs($dirPath, $file, $fileName);
  199. return $fileName;
  200. }
  201. /**
  202. * 生成证书文件名
  203. * @param string $name
  204. * @param string $ext
  205. * @return string
  206. */
  207. private function buildCertFileName(string $name, string $ext): string
  208. {
  209. $hash = str_substr(md5(get_guid_v4()), 8);
  210. return "{$name}-{$hash}.{$ext}";
  211. }
  212. /**
  213. * 删除记录
  214. * @return bool
  215. */
  216. public function setDelete(): bool
  217. {
  218. // 验证模板是否被引用
  219. if (PaymentModel::existsTemplateId($this['template_id'])) {
  220. $this->error = '当前支付模板已被引用,需解除后才可删除';
  221. return false;
  222. }
  223. // 标记为已删除
  224. return $this->save(['is_delete' => 1]);
  225. }
  226. }