<?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. */ /** * Payment.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 EasyWeChat\Core\Exceptions\FaultException; use EasyWeChat\Support\Url as UrlHelper; use EasyWeChat\Support\XML; use Overtrue\Socialite\AccessTokenInterface; use Symfony\Component\HttpFoundation\Response; /** * Class Payment. * * @mixin API */ class Payment { /** * Scheme base path. */ const SCHEME_PATH = 'weixin://wxpay/bizpayurl'; /** * @var API */ protected $api; /** * Merchant instance. * * @var \EasyWeChat\Payment\Merchant */ protected $merchant; /** * Cache. * * @var \Doctrine\Common\Cache\Cache */ protected $cache; /** * 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; } /** * Build payment scheme for product. * * @param string $productId * * @return string */ public function scheme($productId) { $params = [ 'appid' => $this->merchant->app_id, 'mch_id' => $this->merchant->merchant_id, 'time_stamp' => time(), 'nonce_str' => uniqid(), 'product_id' => $productId, ]; $params['sign'] = generate_sign($params, $this->merchant->key, 'md5'); return self::SCHEME_PATH.'?'.http_build_query($params); } /** * Handle payment notify. * * @param callable $callback * * @return Response */ public function handleNotify(callable $callback) { $notify = $this->getNotify(); $signkey = $this->getAPI()->getSignkey(null); if (!$notify->isValid($signkey)) { throw new FaultException('Invalid request payloads.', 400); } $notify = $notify->getNotify(); $successful = 'SUCCESS' === $notify->get('result_code'); $handleResult = call_user_func_array($callback, [$notify, $successful]); if (is_bool($handleResult) && $handleResult) { $response = [ 'return_code' => 'SUCCESS', 'return_msg' => 'OK', ]; } else { $response = [ 'return_code' => 'FAIL', 'return_msg' => $handleResult, ]; } return new Response(XML::build($response)); } /** * Handle refund notify. * * @param callable $callback * * @return Response */ public function handleRefundNotify(callable $callback) { $notify = $this->getRefundNotify()->getNotify(); $successful = 'SUCCESS' === $notify->get('return_code'); $handleResult = call_user_func_array($callback, [$notify, $successful]); if (is_bool($handleResult) && $handleResult) { $response = [ 'return_code' => 'SUCCESS', 'return_msg' => 'OK', ]; } else { $response = [ 'return_code' => 'FAIL', 'return_msg' => $handleResult, ]; } return new Response(XML::build($response)); } /** * Handle native scan notify. * https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4 * The callback shall return string of prepay_id or throw an exception. * * @param callable $callback * * @return Response */ public function handleScanNotify(callable $callback) { $notify = $this->getNotify(); $signkey = $this->getAPI()->getSignkey(null); if (!$notify->isValid($signkey)) { throw new FaultException('Invalid request payloads.', 400); } $notify = $notify->getNotify(); try { $prepayId = call_user_func_array($callback, [$notify->get('product_id'), $notify->get('openid'), $notify]); $response = [ 'return_code' => 'SUCCESS', 'appid' => $this->merchant->app_id, 'mch_id' => $this->merchant->merchant_id, 'nonce_str' => uniqid(), 'prepay_id' => strval($prepayId), 'result_code' => 'SUCCESS', ]; $response['sign'] = generate_sign($response, $this->merchant->key); } catch (\Exception $e) { $response = [ 'return_code' => 'SUCCESS', 'return_msg' => $e->getCode(), 'result_code' => 'FAIL', 'err_code_des' => $e->getMessage(), ]; } return new Response(XML::build($response)); } /** * [WeixinJSBridge] Generate js config for payment. * * <pre> * WeixinJSBridge.invoke( * 'getBrandWCPayRequest', * ... * ); * </pre> * * @param string $prepayId * @param bool $json * * @return string|array */ public function configForPayment($prepayId, $json = true) { $params = [ 'appId' => $this->merchant->app_id, 'timeStamp' => strval(time()), 'nonceStr' => uniqid(), 'package' => "prepay_id=$prepayId", 'signType' => 'MD5', ]; $params['paySign'] = generate_sign($params, $this->merchant->key, 'md5'); return $json ? json_encode($params) : $params; } /** * [JSSDK] Generate js config for payment. * * <pre> * wx.chooseWXPay({...}); * </pre> * * @param string $prepayId * * @return array|string */ public function configForJSSDKPayment($prepayId) { $config = $this->configForPayment($prepayId, false); $config['timestamp'] = $config['timeStamp']; unset($config['timeStamp']); return $config; } /** * Generate app payment parameters. * * @param string $prepayId * * @return array */ public function configForAppPayment($prepayId) { $params = [ 'appid' => $this->merchant->app_id, 'partnerid' => $this->merchant->merchant_id, 'prepayid' => $prepayId, 'noncestr' => uniqid(), 'timestamp' => time(), 'package' => 'Sign=WXPay', ]; $params['sign'] = generate_sign($params, $this->merchant->key); return $params; } /** * Generate js config for share user address. * * @param string|\Overtrue\Socialite\AccessTokenInterface $accessToken * @param bool $json * * @return string|array */ public function configForShareAddress($accessToken, $json = true) { if ($accessToken instanceof AccessTokenInterface) { $accessToken = $accessToken->getToken(); } $params = [ 'appId' => $this->merchant->app_id, 'scope' => 'jsapi_address', 'timeStamp' => strval(time()), 'nonceStr' => uniqid(), 'signType' => 'SHA1', ]; $signParams = [ 'appid' => $params['appId'], 'url' => UrlHelper::current(), 'timestamp' => $params['timeStamp'], 'noncestr' => $params['nonceStr'], 'accesstoken' => strval($accessToken), ]; ksort($signParams); $params['addrSign'] = sha1(urldecode(http_build_query($signParams))); return $json ? json_encode($params) : $params; } /** * Merchant setter. * * @param Merchant $merchant */ public function setMerchant(Merchant $merchant) { $this->merchant = $merchant; } /** * Merchant getter. * * @return Merchant */ public function getMerchant() { return $this->merchant; } /** * Return Notify instance. * * @return \EasyWeChat\Payment\Notify */ public function getNotify() { return new Notify($this->merchant); } /** * Return RefundNotify instance. * * @return \EasyWeChat\Payment\RefundNotify */ public function getRefundNotify() { return new RefundNotify($this->merchant); } /** * API setter. * * @param API $api */ public function setAPI(API $api) { $this->api = $api; } /** * Return API instance. * * @return API */ public function getAPI() { return $this->api ?: $this->api = new API($this->getMerchant(), $this->cache); } /** * Magic call. * * @param string $method * @param array $args * * @return mixed * * @codeCoverageIgnore */ public function __call($method, $args) { if (is_callable([$this->getAPI(), $method])) { return call_user_func_array([$this->api, $method], $args); } } }