PayService.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. <?php
  2. namespace services\extend;
  3. use Yansongda\Pay\Exception\InvalidResponseException;
  4. use Yansongda\Pay\Pay;
  5. use Yii;
  6. use yii\web\UnprocessableEntityHttpException;
  7. use common\enums\PayTypeEnum;
  8. use common\enums\StatusEnum;
  9. use common\enums\PayTradeTypeEnum;
  10. use common\helpers\BcHelper;
  11. use common\helpers\StringHelper;
  12. use common\models\extend\PayLog;
  13. use common\models\extend\PayRefund;
  14. use common\forms\CreditsLogForm;
  15. use common\forms\PayForm;
  16. /**
  17. * Class PayService
  18. * @package services\extend
  19. * @author jianyan74 <751393839@qq.com>
  20. */
  21. class PayService
  22. {
  23. /**
  24. * 微信支付
  25. *
  26. * @param PayForm $payForm
  27. * @param $baseOrder
  28. * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
  29. * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
  30. * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
  31. * @throws \GuzzleHttp\Exception\GuzzleException
  32. */
  33. public function wechat(PayLog $payLog)
  34. {
  35. $order = [
  36. 'out_trade_no' => $payLog->out_trade_no, // 订单号
  37. 'description' => $payLog->body, // 内容
  38. 'amount' => [
  39. 'total' => (int)($payLog->total_fee * 100),
  40. ],
  41. 'notify_url' => $payLog->notify_url, // 通知地址
  42. ];
  43. // 判断如果是公众号/小程序支付
  44. if (in_array($payLog->trade_type, [PayTradeTypeEnum::WECHAT_MP, PayTradeTypeEnum::WECHAT_MINI])) {
  45. $order['payer'] = [
  46. 'openid' => $payLog->openid
  47. ];
  48. }
  49. // 判断如果是手机H5支付
  50. if (in_array($payLog->trade_type, [PayTradeTypeEnum::WECHAT_WAP])) {
  51. $order['scene_info'] = [
  52. 'payer_client_ip' => Yii::$app->request->userIP,
  53. 'h5_info' => [
  54. 'type' => 'Wap', // iOS, Android, Wap
  55. // 'bundle_id' => '', // iOS平台BundleID
  56. // 'package_name' => '', // Android平台PackageName
  57. ],
  58. ];
  59. }
  60. // 判断如果是刷卡支付(V3暂时不支持)
  61. if ($payLog->trade_type == PayTradeTypeEnum::WECHAT_POS) {
  62. $payLog->auth_code = StringHelper::replace('\r', '', $payLog->auth_code);
  63. $payLog->auth_code = StringHelper::replace('\n', '', $payLog->auth_code);
  64. $order['auth_code'] = (int)$payLog->auth_code;
  65. }
  66. // 交易类型
  67. $tradeType = $payLog->trade_type;
  68. return Yii::$app->pay->wechat->$tradeType($order);
  69. }
  70. /**
  71. * 支付宝支付
  72. *
  73. * @param PayLog $payLog
  74. * @return array
  75. * @throws \yii\base\InvalidConfigException
  76. */
  77. public function alipay(PayLog $payLog)
  78. {
  79. // 配置
  80. $config = [
  81. 'notify_url' => $payLog->notify_url, // 支付通知回调地址
  82. ];
  83. // 买家付款成功跳转地址
  84. !empty($payLog->return_url) && $config['return_url'] = $payLog->return_url;
  85. // 生成订单
  86. $order = [
  87. 'out_trade_no' => $payLog->out_trade_no,
  88. 'total_amount' => $payLog->total_fee,
  89. 'subject' => $payLog->body,
  90. ];
  91. // 面对面收款
  92. if ($payLog->trade_type == PayTradeTypeEnum::ALI_POS) {
  93. $payLog->auth_code = StringHelper::replace('\r', '', $payLog->auth_code);
  94. $payLog->auth_code = StringHelper::replace('\n', '', $payLog->auth_code);
  95. $order['auth_code'] = (int)$payLog->auth_code;
  96. }
  97. // 交易类型
  98. $tradeType = $payLog->trade_type;
  99. return [
  100. 'config' => Yii::$app->pay->alipay($config)->$tradeType($order),
  101. ];
  102. }
  103. /**
  104. * 银联支付
  105. *
  106. * @param PayForm $payForm
  107. * @return mixed
  108. * @throws \yii\base\InvalidConfigException
  109. */
  110. public function unipay(PayLog $payLog)
  111. {
  112. // 配置
  113. $config = [
  114. 'notify_url' => $payLog->notify_url, // 支付通知回调地址
  115. 'return_url' => $payLog->return_url, // 买家付款成功跳转地址
  116. ];
  117. // 生成订单
  118. $order = [
  119. 'orderId' => $payLog->out_trade_no, //Your order ID
  120. 'txnTime' => date('YmdHis'), //Should be format 'YmdHis'
  121. 'orderDesc' => $payLog->body, //Order Title
  122. 'txnAmt' => $payLog->total_fee, //Order Total Fee
  123. ];
  124. // 交易类型
  125. $tradeType = $payLog->trade_type;
  126. return Yii::$app->pay->unipay($config)->$tradeType($order);
  127. }
  128. /**
  129. * 字节跳动支付
  130. *
  131. * @param PayForm $payForm
  132. * @return mixed
  133. * @throws \yii\base\InvalidConfigException
  134. */
  135. public function byteDance(PayLog $payLog)
  136. {
  137. // 配置
  138. $config = [
  139. 'notify_url' => $payLog->notify_url, // 支付通知回调地址
  140. 'return_url' => $payLog->return_url, // 买家付款成功跳转地址
  141. ];
  142. // 生成订单
  143. $order = [
  144. 'out_order_no' => $payLog->out_trade_no,
  145. 'body' => $payLog->body,
  146. 'subject' => $payLog->body,
  147. 'total_amount' => (int)BcHelper::mul($payLog->total_fee, 100),
  148. ];
  149. return Yii::$app->pay->byteDance($config)->create($order);
  150. }
  151. /**
  152. * Stripe
  153. *
  154. * @param PayLog $payLog
  155. * @return mixed
  156. */
  157. public function stripe(PayLog $payLog)
  158. {
  159. if (empty($payLog->detail)) {
  160. throw new UnprocessableEntityHttpException('请检查填写信息是否正确');
  161. }
  162. // 生成订单
  163. $order = [
  164. 'amount' => $payLog->total_fee < 1 ? 1 : $payLog->total_fee,
  165. 'currency' => 'CAD',
  166. 'token' => $payLog->detail,
  167. 'description' => $payLog->body,
  168. 'returnUrl' => $payLog->return_url,
  169. 'metadata' => [
  170. 'order_sn' => $payLog->order_sn,
  171. 'out_trade_no' => $payLog->out_trade_no,
  172. ],
  173. // 'paymentMethod' => '',
  174. 'confirm' => true,
  175. ];
  176. // 交易类型
  177. $tradeType = $payLog->trade_type;
  178. return Yii::$app->pay->stripe->$tradeType($order);
  179. }
  180. /**
  181. * 退款申请
  182. *
  183. * @param $order_sn
  184. * @param int $money
  185. * @throws UnprocessableEntityHttpException
  186. * @throws \Omnipay\Common\Exception\InvalidRequestException
  187. */
  188. public function refundAll($order_sn, $money = 0)
  189. {
  190. $refundMoneyStatus = !empty($money);
  191. $models = $this->findAllByOrderSn($order_sn);
  192. /** @var PayLog $model */
  193. foreach ($models as $model) {
  194. // 退固定金额
  195. if ($refundMoneyStatus == true) {
  196. if ($money > 0) {
  197. // 退款金额大于余额
  198. if ($money > $model->pay_fee) {
  199. $this->refund($model->pay_type, $model->pay_fee, $order_sn, $model);
  200. $money = BcHelper::sub($money, $model->pay_fee);
  201. } else {
  202. $this->refund($model->pay_type, $money, $order_sn, $model);
  203. $money = 0;
  204. }
  205. }
  206. } else {
  207. $this->refund($model->pay_type, $model->pay_fee, $order_sn, $model);
  208. }
  209. }
  210. }
  211. /**
  212. * 订单退款
  213. *
  214. * @param $pay_type
  215. * @param $money
  216. * @param $out_trade_no
  217. * @throws UnprocessableEntityHttpException
  218. * @throws \Omnipay\Common\Exception\InvalidRequestException
  219. */
  220. public function refund($pay_type, $money, $order_sn, $model = '')
  221. {
  222. /** @var PayLog $model */
  223. if (empty($model)) {
  224. $model = $this->findByOrderSn($order_sn);
  225. }
  226. if (!$model) {
  227. throw new UnprocessableEntityHttpException('找不到支付记录');
  228. }
  229. if ($model->pay_status == StatusEnum::DISABLED) {
  230. throw new UnprocessableEntityHttpException('未支付');
  231. }
  232. $residueMoney = BcHelper::sub($model->pay_fee, $this->getRefundMoneyByPayId($model->id));
  233. if ($money > $residueMoney) {
  234. throw new UnprocessableEntityHttpException('退款金额不可大于支付金额');
  235. }
  236. $refund_sn = date('YmdHis') . StringHelper::random(8, true);
  237. $response = [];
  238. // 字节跳动小程序退款
  239. if ($model['trade_type'] == 'byte-dance') {
  240. $response = Yii::$app->pay->byteDance->refund([
  241. 'out_order_no' => $model->out_trade_no,
  242. 'out_refund_no' => $refund_sn,
  243. 'refund_amount' => $money * 100,
  244. 'reason' => '不想要了',
  245. ]);
  246. } else {
  247. switch ($pay_type) {
  248. case PayTypeEnum::WECHAT :
  249. $total_fee = $this->getTotalFeeByOutTradeNo($model->out_trade_no);
  250. $info = [
  251. 'out_trade_no' => $model->out_trade_no,
  252. 'out_refund_no' => $refund_sn,
  253. 'amount' => [
  254. 'refund' => $money * 100,
  255. 'total' => (int) ($total_fee * 100),
  256. 'currency' => 'CNY',
  257. ],
  258. ];
  259. $response = Yii::$app->pay->wechat->refund($info);
  260. $response = $response->toArray();
  261. if (isset($response['code'])) {
  262. throw new UnprocessableEntityHttpException($response['message']);
  263. }
  264. break;
  265. case PayTypeEnum::ALI :
  266. $info = [
  267. 'out_trade_no' => $model->out_trade_no,
  268. 'trade_no' => $model->transaction_id,
  269. 'refund_amount' => $money,
  270. 'out_request_no' => $refund_sn,
  271. ];
  272. $response = Yii::$app->pay->alipay->refund($info);
  273. $response = $response->toArray();
  274. if (isset($response['code'])) {
  275. throw new UnprocessableEntityHttpException($response['message']);
  276. }
  277. break;
  278. case PayTypeEnum::UNION :
  279. $info = [
  280. 'txnTime' => date('YmdHis'),
  281. 'origQryId' => $model->transaction_id,
  282. 'txnAmt' => $money,
  283. 'orderId' => $refund_sn,
  284. ];
  285. $response = Yii::$app->pay->unipay->refund($info);
  286. $response = $response->toArray();
  287. if (isset($response['code'])) {
  288. throw new UnprocessableEntityHttpException($response['message']);
  289. }
  290. break;
  291. }
  292. }
  293. $model->refund_fee += $money;
  294. $model->save();
  295. $refund = new PayRefund();
  296. $refund = $refund->loadDefaultValues();
  297. $refund->pay_id = $model->id;
  298. $refund->app_id = Yii::$app->id;
  299. $refund->ip = Yii::$app->services->base->getUserIp();
  300. $refund->order_sn = $order_sn;
  301. $refund->merchant_id = $model->merchant_id;
  302. $refund->member_id = $model->member_id;
  303. $refund->refund_trade_no = $refund_sn;
  304. $refund->refund_money = $money;
  305. $refund->save();
  306. }
  307. /**
  308. * 支付通知回调
  309. *
  310. * @param PayLog $log
  311. * @param $paymentType
  312. * @throws \yii\web\NotFoundHttpException
  313. */
  314. public function notify(PayLog $log)
  315. {
  316. $log->pay_ip = Yii::$app->request->userIP;
  317. $log->save();
  318. switch ($log->order_group) {
  319. case 'order' :
  320. // TODO 处理订单
  321. // 记录消费日志
  322. Yii::$app->services->memberCreditsLog->consumeMoney(new CreditsLogForm([
  323. 'member' => Yii::$app->services->member->get($log->member_id),
  324. 'num' => $log->pay_fee,
  325. 'group' => 'order',
  326. 'pay_type' => $log->pay_type,
  327. 'remark' => "订单支付",
  328. 'map_id' => $log->id,
  329. ]));
  330. break;
  331. case 'recharge' :
  332. $payFee = $log['pay_fee'];
  333. $member = Yii::$app->services->member->get($log['member_id']);
  334. // 充值
  335. Yii::$app->services->memberCreditsLog->incrMoney(new CreditsLogForm([
  336. 'member' => $member,
  337. 'pay_type' => $log['pay_type'],
  338. 'num' => $payFee,
  339. 'group' => 'recharge',
  340. 'remark' => "在线充值",
  341. 'map_id' => $log['id'],
  342. ]));
  343. // TODO 赠送
  344. break;
  345. }
  346. }
  347. /**
  348. * @param $pay_id
  349. * @return bool|int|mixed|string|null
  350. */
  351. public function getRefundMoneyByPayId($pay_id)
  352. {
  353. $money = PayRefund::find()
  354. ->where(['pay_id' => $pay_id])
  355. ->sum('refund_money');
  356. return empty($money) ? 0 : $money;
  357. }
  358. /**
  359. * @param $outTradeNo
  360. * @return array|null|\yii\db\ActiveRecord|PayLog
  361. */
  362. public function findByOutTradeNo($outTradeNo)
  363. {
  364. return PayLog::find()
  365. ->where(['out_trade_no' => $outTradeNo])
  366. ->one();
  367. }
  368. /**
  369. * @param $outTradeNo
  370. * @return array|null|\yii\db\ActiveRecord|PayLog
  371. */
  372. public function findOrderSnByOutTradeNo($outTradeNo)
  373. {
  374. return PayLog::find()
  375. ->select(['order_sn'])
  376. ->where(['out_trade_no' => $outTradeNo])
  377. ->column();
  378. }
  379. /**
  380. * 获取订单总金额
  381. *
  382. * @param $outTradeNo
  383. * @return bool|int|mixed|string|null
  384. */
  385. public function getTotalFeeByOutTradeNo($outTradeNo)
  386. {
  387. return PayLog::find()
  388. ->select('pay_fee')
  389. ->where(['out_trade_no' => $outTradeNo])
  390. ->orderBy('id desc')
  391. ->scalar();
  392. }
  393. /**
  394. * @param $order_sn
  395. * @return array|\yii\db\ActiveRecord|null
  396. */
  397. public function findByOrderSn($order_sn)
  398. {
  399. return PayLog::find()
  400. ->where(['order_sn' => $order_sn])
  401. ->one();
  402. }
  403. /**
  404. * @param $order_sn
  405. * @return array|\yii\db\ActiveRecord|null
  406. */
  407. public function findAllByOrderSn($order_sn)
  408. {
  409. return PayLog::find()
  410. ->where(['order_sn' => $order_sn, 'pay_status' => StatusEnum::ENABLED])
  411. ->all();
  412. }
  413. }
粤ICP备19079148号