| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645 |
- <?php
- /*
- * This file is part of the overtrue/wechat.
- *
- * (c) overtrue <i@overtrue.me>
- *
- * This source file is subject to the MIT license that is bundled
- * with this source code in the file LICENSE.
- */
- /**
- * API.php.
- *
- * @author overtrue <i@overtrue.me>
- * @copyright 2015 overtrue <i@overtrue.me>
- *
- * @see https://github.com/overtrue
- * @see http://overtrue.me
- */
- namespace EasyWeChat\Payment;
- use Doctrine\Common\Cache\Cache;
- use Doctrine\Common\Cache\FilesystemCache;
- use EasyWeChat\Core\AbstractAPI;
- use EasyWeChat\Core\Exception;
- use EasyWeChat\Support\Collection;
- use EasyWeChat\Support\Log;
- use EasyWeChat\Support\XML;
- use Psr\Http\Message\ResponseInterface;
- /**
- * Class API.
- */
- class API extends AbstractAPI
- {
- /**
- * Merchant instance.
- *
- * @var Merchant
- */
- protected $merchant;
- /**
- * Sandbox box mode.
- *
- * @var bool
- */
- protected $sandboxEnabled = false;
- /**
- * Sandbox sign key.
- *
- * @var string
- */
- protected $sandboxSignKey;
- /**
- * Cache.
- *
- * @var \Doctrine\Common\Cache\Cache
- */
- protected $cache;
- const API_HOST = 'https://api.mch.weixin.qq.com';
- // api
- const API_PAY_ORDER = '/pay/micropay';
- const API_PREPARE_ORDER = '/pay/unifiedorder';
- const API_QUERY = '/pay/orderquery';
- const API_CLOSE = '/pay/closeorder';
- const API_REVERSE = '/secapi/pay/reverse';
- const API_REFUND = '/secapi/pay/refund';
- const API_QUERY_REFUND = '/pay/refundquery';
- const API_DOWNLOAD_BILL = '/pay/downloadbill';
- const API_REPORT = '/payitil/report';
- const API_PROFITSHARING = '/secapi/pay/profitsharing';
- const API_MULTI_PROFIT_SHARING = '/secapi/pay/multiprofitsharing';
- const API_PROFIT_SHARING_ADD_RECEIVER = '/secapi/pay/profitsharingaddreceiver';
- const API_PROFIT_SHARING_FINISH = '/secapi/pay/profitsharingfinish';
- const API_PROFIT_SHARING_QUERY = '/pay/profitsharingquery';
- const API_URL_SHORTEN = 'https://api.mch.weixin.qq.com/tools/shorturl';
- const API_AUTH_CODE_TO_OPENID = 'https://api.mch.weixin.qq.com/tools/authcodetoopenid';
- const API_SANDBOX_SIGN_KEY = 'https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey';
- // order id types.
- const TRANSACTION_ID = 'transaction_id';
- const OUT_TRADE_NO = 'out_trade_no';
- const OUT_REFUND_NO = 'out_refund_no';
- const REFUND_ID = 'refund_id';
- // bill types.
- const BILL_TYPE_ALL = 'ALL';
- const BILL_TYPE_SUCCESS = 'SUCCESS';
- const BILL_TYPE_REFUND = 'REFUND';
- const BILL_TYPE_REVOKED = 'REVOKED';
- /**
- * API constructor.
- *
- * @param \EasyWeChat\Payment\Merchant $merchant
- * @param \Doctrine\Common\Cache\Cache|null $cache
- */
- public function __construct(Merchant $merchant, Cache $cache = null)
- {
- $this->merchant = $merchant;
- $this->cache = $cache;
- }
- /**
- * Pay the order.
- *
- * @param Order $order
- *
- * @return \EasyWeChat\Support\Collection
- */
- public function pay(Order $order)
- {
- return $this->request($this->wrapApi(self::API_PAY_ORDER), $order->all());
- }
- /**
- * Prepare order to pay.
- *
- * @param Order $order
- *
- * @return \EasyWeChat\Support\Collection
- */
- public function prepare(Order $order)
- {
- $order->notify_url = $order->get('notify_url', $this->merchant->notify_url);
- $order->profit_sharing = $order->get('profit_sharing', $this->merchant->profit_sharing);
- if (is_null($order->spbill_create_ip)) {
- $order->spbill_create_ip = (Order::NATIVE === $order->trade_type) ? get_server_ip() : get_client_ip();
- }
- return $this->request($this->wrapApi(self::API_PREPARE_ORDER), $order->all());
- }
- /**
- * Query order.
- *
- * @param string $orderNo
- * @param string $type
- *
- * @return \EasyWeChat\Support\Collection
- */
- public function query($orderNo, $type = self::OUT_TRADE_NO)
- {
- $params = [
- $type => $orderNo,
- ];
- return $this->request($this->wrapApi(self::API_QUERY), $params);
- }
- /**
- * Query order by transaction_id.
- *
- * @param string $transactionId
- *
- * @return \EasyWeChat\Support\Collection
- */
- public function queryByTransactionId($transactionId)
- {
- return $this->query($transactionId, self::TRANSACTION_ID);
- }
- /**
- * Close order by out_trade_no.
- *
- * @param $tradeNo
- *
- * @return \EasyWeChat\Support\Collection
- */
- public function close($tradeNo)
- {
- $params = [
- 'out_trade_no' => $tradeNo,
- ];
- return $this->request($this->wrapApi(self::API_CLOSE), $params);
- }
- /**
- * Reverse order.
- *
- * @param string $orderNo
- * @param string $type
- *
- * @return \EasyWeChat\Support\Collection
- */
- public function reverse($orderNo, $type = self::OUT_TRADE_NO)
- {
- $params = [
- $type => $orderNo,
- ];
- return $this->safeRequest($this->wrapApi(self::API_REVERSE), $params);
- }
- /**
- * Reverse order by transaction_id.
- *
- * @param int $transactionId
- *
- * @return \EasyWeChat\Support\Collection
- */
- public function reverseByTransactionId($transactionId)
- {
- return $this->reverse($transactionId, self::TRANSACTION_ID);
- }
- /**
- * Make a refund request.
- *
- * @param string $orderNo
- * @param string $refundNo
- * @param float $totalFee
- * @param float $refundFee
- * @param string $opUserId
- * @param string $type
- * @param string $refundAccount
- * @param string $refundReason
- *
- * @return Collection
- */
- public function refund(
- $orderNo,
- $refundNo,
- $totalFee,
- $refundFee = null,
- $opUserId = null,
- $type = self::OUT_TRADE_NO,
- $refundAccount = 'REFUND_SOURCE_UNSETTLED_FUNDS',
- $refundReason = ''
- ) {
- $params = [
- $type => $orderNo,
- 'out_refund_no' => $refundNo,
- 'total_fee' => $totalFee,
- 'refund_fee' => $refundFee ?: $totalFee,
- 'refund_fee_type' => $this->merchant->fee_type,
- 'refund_account' => $refundAccount,
- 'refund_desc' => $refundReason,
- 'op_user_id' => $opUserId ?: $this->merchant->merchant_id,
- ];
- return $this->safeRequest($this->wrapApi(self::API_REFUND), $params);
- }
- /**
- * Refund by transaction id.
- *
- * @param string $orderNo
- * @param string $refundNo
- * @param float $totalFee
- * @param float $refundFee
- * @param string $opUserId
- * @param string $refundAccount
- * @param string $refundReason
- *
- * @return Collection
- */
- public function refundByTransactionId(
- $orderNo,
- $refundNo,
- $totalFee,
- $refundFee = null,
- $opUserId = null,
- $refundAccount = 'REFUND_SOURCE_UNSETTLED_FUNDS',
- $refundReason = ''
- ) {
- return $this->refund($orderNo, $refundNo, $totalFee, $refundFee, $opUserId, self::TRANSACTION_ID, $refundAccount, $refundReason);
- }
- /**
- * Query refund status.
- *
- * @param string $orderNo
- * @param string $type
- *
- * @return \EasyWeChat\Support\Collection
- */
- public function queryRefund($orderNo, $type = self::OUT_TRADE_NO)
- {
- $params = [
- $type => $orderNo,
- ];
- return $this->request($this->wrapApi(self::API_QUERY_REFUND), $params);
- }
- /**
- * Query refund status by out_refund_no.
- *
- * @param string $refundNo
- *
- * @return \EasyWeChat\Support\Collection
- */
- public function queryRefundByRefundNo($refundNo)
- {
- return $this->queryRefund($refundNo, self::OUT_REFUND_NO);
- }
- /**
- * Query refund status by transaction_id.
- *
- * @param string $transactionId
- *
- * @return \EasyWeChat\Support\Collection
- */
- public function queryRefundByTransactionId($transactionId)
- {
- return $this->queryRefund($transactionId, self::TRANSACTION_ID);
- }
- /**
- * Query refund status by refund_id.
- *
- * @param string $refundId
- *
- * @return \EasyWeChat\Support\Collection
- */
- public function queryRefundByRefundId($refundId)
- {
- return $this->queryRefund($refundId, self::REFUND_ID);
- }
- /**
- * Download bill history as a table file.
- *
- * @param string $date
- * @param string $type
- *
- * @return \Psr\Http\Message\ResponseInterface
- */
- public function downloadBill($date, $type = self::BILL_TYPE_ALL)
- {
- $params = [
- 'bill_date' => $date,
- 'bill_type' => $type,
- ];
- return $this->request($this->wrapApi(self::API_DOWNLOAD_BILL), $params, 'post', [\GuzzleHttp\RequestOptions::STREAM => true], true)->getBody();
- }
- /**
- * Convert long url to short url.
- *
- * @param string $url
- *
- * @return \EasyWeChat\Support\Collection
- */
- public function urlShorten($url)
- {
- return $this->request(self::API_URL_SHORTEN, ['long_url' => $url]);
- }
- /**
- * Report API status to WeChat.
- *
- * @param string $api
- * @param int $timeConsuming
- * @param string $resultCode
- * @param string $returnCode
- * @param array $other ex: err_code,err_code_des,out_trade_no,user_ip...
- *
- * @return \EasyWeChat\Support\Collection
- */
- public function report($api, $timeConsuming, $resultCode, $returnCode, array $other = [])
- {
- $params = array_merge([
- 'interface_url' => $api,
- 'execute_time_' => $timeConsuming,
- 'return_code' => $returnCode,
- 'return_msg' => null,
- 'result_code' => $resultCode,
- 'user_ip' => get_client_ip(),
- 'time' => time(),
- ], $other);
- return $this->request($this->wrapApi(self::API_REPORT), $params);
- }
- /**
- * Get openid by auth code.
- *
- * @param string $authCode
- *
- * @return \EasyWeChat\Support\Collection
- */
- public function authCodeToOpenId($authCode)
- {
- return $this->request(self::API_AUTH_CODE_TO_OPENID, ['auth_code' => $authCode]);
- }
- /**
- * Merchant setter.
- *
- * @param Merchant $merchant
- *
- * @return $this
- */
- public function setMerchant(Merchant $merchant)
- {
- $this->merchant = $merchant;
- }
- /**
- * Merchant getter.
- *
- * @return Merchant
- */
- public function getMerchant()
- {
- return $this->merchant;
- }
- /**
- * Set sandbox mode.
- *
- * @param bool $enabled
- *
- * @return $this
- */
- public function sandboxMode($enabled = false)
- {
- $this->sandboxEnabled = $enabled;
- return $this;
- }
- /**
- * Make a API request.
- *
- * @param string $api
- * @param array $params
- * @param string $method
- * @param array $options
- * @param bool $returnResponse
- *
- * @return \EasyWeChat\Support\Collection|\Psr\Http\Message\ResponseInterface
- */
- protected function request($api, array $params, $method = 'post', array $options = [], $returnResponse = false, $sign_type = 'md5')
- {
- $params = array_merge($params, $this->merchant->only(['sub_appid', 'sub_mch_id']));
- $params['appid'] = $this->merchant->app_id;
- $params['mch_id'] = $this->merchant->merchant_id;
- $params['device_info'] = $this->merchant->device_info;
- $params['nonce_str'] = uniqid();
- $params = array_filter($params);
- $params['sign'] = generate_sign($params, $this->getSignkey($api), $sign_type);
- $options = array_merge([
- 'body' => XML::build($params),
- ], $options);
- $response = $this->getHttp()->request($api, $method, $options);
- return $returnResponse ? $response : $this->parseResponse($response);
- }
- /**
- * Return key to sign.
- *
- * @param string $api
- *
- * @return string
- */
- public function getSignkey($api)
- {
- return $this->sandboxEnabled && self::API_SANDBOX_SIGN_KEY !== $api ? $this->getSandboxSignKey() : $this->merchant->key;
- }
- /**
- * Request with SSL.
- *
- * @param string $api
- * @param array $params
- * @param string $method
- *
- * @return \EasyWeChat\Support\Collection
- */
- protected function safeRequest($api, array $params, $method = 'post', $sign_type = 'md5')
- {
- $options = [
- 'cert' => $this->merchant->get('cert_path'),
- 'ssl_key' => $this->merchant->get('key_path'),
- ];
- return $this->request($api, $params, $method, $options,false, $sign_type);
- }
- /**
- * Parse Response XML to array.
- *
- * @param ResponseInterface $response
- *
- * @return \EasyWeChat\Support\Collection
- */
- protected function parseResponse($response)
- {
- if ($response instanceof ResponseInterface) {
- $response = $response->getBody();
- }
- return new Collection((array) XML::parse($response));
- }
- /**
- * Wrap API.
- *
- * @param string $resource
- *
- * @return string
- */
- protected function wrapApi($resource)
- {
- return self::API_HOST.($this->sandboxEnabled ? '/sandboxnew' : '').$resource;
- }
- /**
- * Get sandbox sign key.
- *
- * @return string
- */
- protected function getSandboxSignKey()
- {
- if ($this->sandboxSignKey) {
- return $this->sandboxSignKey;
- }
- // Try to get sandbox_signkey from cache
- $cacheKey = 'sandbox_signkey.'.$this->merchant->merchant_id.$this->merchant->sub_merchant_id;
- /** @var \Doctrine\Common\Cache\Cache $cache */
- $cache = $this->getCache();
- $this->sandboxSignKey = $cache->fetch($cacheKey);
- if (!$this->sandboxSignKey) {
- // Try to acquire a new sandbox_signkey from WeChat
- $result = $this->request(self::API_SANDBOX_SIGN_KEY, []);
- if ('SUCCESS' === $result->return_code) {
- $cache->save($cacheKey, $result->sandbox_signkey, 24 * 3600);
- return $this->sandboxSignKey = $result->sandbox_signkey;
- }
- throw new Exception($result->return_msg);
- }
- return $this->sandboxSignKey;
- }
- /**
- * Return the cache manager.
- *
- * @return \Doctrine\Common\Cache\Cache
- */
- public function getCache()
- {
- return $this->cache ?: $this->cache = new FilesystemCache(sys_get_temp_dir());
- }
- /**
- * @param $orderNo
- * @param $sharingNo
- * @param $receivers
- * @param string $type
- * @return Collection
- */
- public function profit_sharing(
- $orderNo,
- $sharingNo,
- $receivers,
- $type = self::TRANSACTION_ID
- )
- {
- $params = [
- $type => $orderNo,
- 'out_order_no' => $sharingNo,
- 'receivers' => json_encode($receivers)
- ];
- return $this->safeRequest($this->wrapApi(self::API_PROFITSHARING), $params, 'post','hash_hmac');
- }
- public function addReceiver(
- $receiver
- )
- {
- $params = [
- 'receiver' => json_encode($receiver)
- ];
- return $this->safeRequest($this->wrapApi(self::API_PROFIT_SHARING_ADD_RECEIVER), $params, 'post', 'hash_hmac');
- }
- public function multiProfitSharing(
- $orderNo,
- $sharingNo,
- $receivers,
- $type = self::TRANSACTION_ID
- )
- {
- $params = [
- $type => $orderNo,
- 'out_order_no' => $sharingNo,
- 'receivers' => json_encode($receivers)
- ];
- return $this->safeRequest($this->wrapApi(self::API_MULTI_PROFIT_SHARING), $params, 'post', 'hash_hmac');
- }
- public function profitSharingFinish(
- $orderNo,
- $sharingNo,
- $type = self::TRANSACTION_ID
- )
- {
- $params = [
- $type => $orderNo,
- 'out_order_no' => $sharingNo,
- 'description' => '分账已完成'
- ];
- return $this->safeRequest($this->wrapApi(self::API_PROFIT_SHARING_FINISH), $params, 'post', 'hash_hmac');
- }
- public function profitSharingQuery(
- $orderNo,
- $sharingNo,
- $type = self::TRANSACTION_ID
- )
- {
- $params = [
- $type => $orderNo,
- 'out_order_no' => $sharingNo,
- ];
- return $this->safeRequest($this->wrapApi(self::API_PROFIT_SHARING_QUERY), $params, 'post', 'hash_hmac');
- }
- }
|