| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540 | <?phpnamespace addons\epay\library;use addons\third\model\Third;use app\common\library\Auth;use Exception;use think\Hook;use think\Session;use Yansongda\Pay\Pay;use Yansongda\Supports\Str;/** * 订单服务类 * * @package addons\epay\library */class Service{    public const SDK_VERSION_V2 = 'v2';    public const SDK_VERSION_V3 = 'v3';    /**     * 提交退款订单     * @param array|float $amount    订单金额     * @param array|float $refund_money    退款金额     * @param string      $orderid   订单号     * @param string      $refund_sn   退款订单号     * @param string      $type      支付类型,可选alipay或wechat     * @param string      $remark     退款原因     * @param string      $notifyurl 通知回调URL     * @param string      $returnurl 跳转返回URL     * @param string      $method    支付方式     * @return Response|RedirectResponse|Collection     * @throws Exception     */    public static function submitRefund($amount=null,$refund_money,$orderid,$refund_sn,$type,$remark = null,$notifyurl = null,$returnurl = null,$method = 'app'){        if (!is_array($amount)) {            $params = [                'amount'    => $amount,                'type'      => $type,                'notifyurl' => $notifyurl,                'returnurl' => $returnurl,                'method'    => $method,            ];        } else {            $params = $amount;        }        $type = isset($params['type']) && in_array($params['type'], ['alipay', 'wechat']) ? $params['type'] : 'wechat';        $request = request();        $notifyurl = isset($params['notifyurl']) ? $params['notifyurl'] : $request->root(true) . '/addons/epay/index/' . $type . 'notify';        // $returnurl = isset($params['returnurl']) ? $params['returnurl'] : $request->root(true) . '/addons/epay/index/' . $type . 'return/out_trade_no/' . $orderid;        $config = Service::getConfig($type);        $config['notify_url'] = $notifyurl;        $config['return_url'] = $returnurl;        $result = null;        //退款参数        $order_data = [            'out_trade_no' => $orderid//原订单号        ];        if ($type == 'wechat') {            //创建支付对象            $pay = Pay::wechat($config);            $total_fee = $amount * 100;            $refund_fee = $refund_money * 100;            $order_data = array_merge($order_data, [                'out_refund_no' => $refund_sn,//退款订单号                'total_fee' => $total_fee,//支付金额                'refund_fee' => $refund_fee,//退款金额                'refund_desc' => $remark,//退款原因                //'type' => $method  //支付方式            ]);        } else {            $pay = Pay::alipay($config);            $order_data = array_merge($order_data, [                'out_request_no' => $refund_sn,//退款订单号                'refund_amount' => $refund_money,            ]);        }        $result = $pay->refund($order_data);        //使用重写的Response类、RedirectResponse、Collection类        if ($result instanceof \Symfony\Component\HttpFoundation\RedirectResponse) {            $result = new RedirectResponse($result->getTargetUrl());        } elseif ($result instanceof \Symfony\Component\HttpFoundation\Response) {            $result = new Response($result->getContent());        } elseif ($result instanceof \Yansongda\Supports\Collection) {            $result = Collection::make($result->all());        } elseif ($result instanceof \GuzzleHttp\Psr7\Response) {            $result = new Response($result->getBody());        }        return $result;    }    /**     * 提交订单     * @param array|float $amount    订单金额     * @param string      $orderid   订单号     * @param string      $type      支付类型,可选alipay或wechat     * @param string      $title     订单标题     * @param string      $notifyurl 通知回调URL     * @param string      $returnurl 跳转返回URL     * @param string      $method    支付方法     * @param string      $openid    Openid     * @param array       $custom    自定义微信支付宝相关配置     * @return Response|RedirectResponse|Collection     * @throws Exception     */    public static function submitOrder($amount, $orderid = null, $type = null, $title = null, $notifyurl = null, $returnurl = null, $method = null, $openid = '', $custom = [])    {        $version = self::getSdkVersion();        $request = request();        $addonConfig = get_addon_config('epay');        if (!is_array($amount)) {            $params = [                'amount'    => $amount,                'orderid'   => $orderid,                'type'      => $type,                'title'     => $title,                'notifyurl' => $notifyurl,                'returnurl' => $returnurl,                'method'    => $method,                'openid'    => $openid,                'custom'    => $custom,            ];        } else {            $params = $amount;        }        $type = isset($params['type']) && in_array($params['type'], ['alipay', 'wechat']) ? $params['type'] : 'wechat';        $method = $params['method'] ?? 'web';        $orderid = $params['orderid'] ?? date("YmdHis") . mt_rand(100000, 999999);        $amount = $params['amount'] ?? 1;        $title = $params['title'] ?? "支付";        $auth_code = $params['auth_code'] ?? '';        $openid = $params['openid'] ?? '';        //自定义微信支付宝相关配置        $custom = $params['custom'] ?? [];        //未定义则使用默认回调和跳转        $notifyurl = !empty($params['notifyurl']) ? $params['notifyurl'] : $request->root(true) . '/addons/epay/index/notifyx/paytype/' . $type;        $returnurl = !empty($params['returnurl']) ? $params['returnurl'] : $request->root(true) . '/addons/epay/index/returnx/paytype/' . $type . '/out_trade_no/' . $orderid;        $html = '';        $config = Service::getConfig($type, array_merge($custom, ['notify_url' => $notifyurl, 'return_url' => $returnurl]));        //判断是否移动端或微信内浏览器        $isMobile = $request->isMobile();        $isWechat = strpos($request->server('HTTP_USER_AGENT'), 'MicroMessenger') !== false;        $result = null;        if ($type == 'alipay') {            //如果是PC支付,判断当前环境,进行跳转            if ($method == 'web') {                //如果是微信环境或后台配置PC使用扫码支付                if ($isWechat || $addonConfig['alipay']['scanpay']) {                    Session::set("alipayorderdata", $params);                    $url = addon_url('epay/api/alipay', [], true, true);                    return new RedirectResponse($url);                } elseif ($isMobile) {                    $method = 'wap';                }            }            //创建支付对象            $pay = Pay::alipay($config);            $params = [                'out_trade_no' => $orderid,//你的订单号                'total_amount' => $amount,//单位元                'subject'      => $title,            ];            switch ($method) {                case 'web':                    //电脑支付                    $result = $pay->web($params);                    break;                case 'wap':                    //手机网页支付                    $result = $pay->wap($params);                    break;                case 'app':                    //APP支付                    $result = $pay->app($params);                    break;                case 'scan':                    //扫码支付                    $result = $pay->scan($params);                    break;                case 'pos':                    //刷卡支付必须要有auth_code                    $params['auth_code'] = $auth_code;                    $result = $pay->pos($params);                    break;                case 'mini':                case 'miniapp':                    //小程序支付                    //小程序支付,直接返回字符串                    //小程序支付必须要有buyer_id,这里使用openid                    $params['buyer_id'] = $openid;                    $result = $pay->mini($params);                    break;                default:            }        } else {            //如果是PC支付,判断当前环境,进行跳转            if ($method == 'web') {                //如果是移动端,但不是微信环境                if ($isMobile && !$isWechat) {                    $method = 'wap';                } else {                    Session::set("wechatorderdata", $params);                    $url = addon_url('epay/api/wechat', [], true, true);                    return new RedirectResponse($url);                }            }            //单位分            $total_fee = function_exists('bcmul') ? bcmul($amount, 100) : $amount * 100;            $total_fee = (int)$total_fee;            $ip = $request->ip();            //微信服务商模式时需传递sub_openid参数            $openidName = $addonConfig['wechat']['mode'] == 'service' ? 'sub_openid' : 'openid';            //创建支付对象            $pay = Pay::wechat($config);            if (self::isVersionV3()) {                //V3支付                $params = [                    'out_trade_no' => $orderid,                    'description'  => $title,                    'amount'       => [                        'total' => $total_fee,                    ]                ];                switch ($method) {                    case 'mp':                        //公众号支付                        //公众号支付必须有openid                        $params['payer'] = [$openidName => $openid];                        $result = $pay->mp($params);                        break;                    case 'wap':                        //手机网页支付,跳转                        $params['scene_info'] = [                            'payer_client_ip' => $ip,                            'h5_info'         => [                                'type' => 'Wap',                            ]                        ];                        $result = $pay->wap($params);                        break;                    case 'app':                        //APP支付,直接返回字符串                        $result = $pay->app($params);                        break;                    case 'scan':                        //扫码支付,直接返回字符串                        $result = $pay->scan($params);                        break;                    case 'pos':                        //刷卡支付,直接返回字符串                        //刷卡支付必须要有auth_code                        $params['auth_code'] = $auth_code;                        $result = $pay->pos($params);                        break;                    case 'mini':                    case 'miniapp':                        //小程序支付,直接返回字符串                        //小程序支付必须要有openid                        $params['payer'] = [$openidName => $openid];                        $result = $pay->mini($params);                        break;                    default:                }            } else {                //V2支付                $params = [                    'out_trade_no' => $orderid,                    'body'         => $title,                    'total_fee'    => $total_fee,                ];                switch ($method) {                    case 'mp':                        //公众号支付                        //公众号支付必须有openid                        $params[$openidName] = $openid;                        $result = $pay->mp($params);                        break;                    case 'wap':                        //手机网页支付,跳转                        $params['spbill_create_ip'] = $ip;                        $result = $pay->wap($params);                        break;                    case 'app':                        //APP支付,直接返回字符串                        $result = $pay->app($params);                        break;                    case 'scan':                        //扫码支付,直接返回字符串                        $result = $pay->scan($params);                        break;                    case 'pos':                        //刷卡支付,直接返回字符串                        //刷卡支付必须要有auth_code                        $params['auth_code'] = $auth_code;                        $result = $pay->pos($params);                        break;                    case 'mini':                    case 'miniapp':                        //小程序支付,直接返回字符串                        //小程序支付必须要有openid                        $params[$openidName] = $openid;                        $result = $pay->miniapp($params);                        break;                    default:                }            }        }        //使用重写的Response类、RedirectResponse、Collection类        if ($result instanceof \Symfony\Component\HttpFoundation\RedirectResponse) {            $result = new RedirectResponse($result->getTargetUrl());        } elseif ($result instanceof \Symfony\Component\HttpFoundation\Response) {            $result = new Response($result->getContent());        } elseif ($result instanceof \Yansongda\Supports\Collection) {            $result = Collection::make($result->all());        } elseif ($result instanceof \GuzzleHttp\Psr7\Response) {            $result = new Response($result->getBody());        }        return $result;    }    /**     * 验证回调是否成功     * @param string $type   支付类型     * @param array  $custom 自定义配置信息     * @return bool|\Yansongda\Pay\Gateways\Alipay|\Yansongda\Pay\Gateways\Wechat|\Yansongda\Pay\Provider\Wechat|\Yansongda\Pay\Provider\Alipay     */    public static function checkNotify($type, $custom = [])    {        $type = strtolower($type);        if (!in_array($type, ['wechat', 'alipay'])) {            return false;        }        $version = self::getSdkVersion();        try {            $config = self::getConfig($type, $custom);            $pay = $type == 'wechat' ? Pay::wechat($config) : Pay::alipay($config);            $data = Service::isVersionV3() ? $pay->callback() : $pay->verify();            if ($type == 'alipay') {                if (in_array($data['trade_status'], ['TRADE_SUCCESS', 'TRADE_FINISHED'])) {                    return $pay;                }            } else {                return $pay;            }        } catch (Exception $e) {            \think\Log::record("回调请求参数解析错误", "error");            return false;        }        return false;    }    /**     * 验证返回是否成功,请勿用于判断是否支付成功的逻辑验证     * 已弃用     *     * @param string $type   支付类型     * @param array  $custom 自定义配置信息     * @return bool     * @deprecated  已弃用,请勿用于逻辑验证     */    public static function checkReturn($type, $custom = [])    {        //由于PC及移动端无法获取请求的参数信息,取消return验证,均返回true        return true;    }    /**     * 获取配置     * @param string $type   支付类型     * @param array  $custom 自定义配置,用于覆盖插件默认配置     * @return array     */    public static function getConfig($type = 'wechat', $custom = [])    {        $addonConfig = get_addon_config('epay');        $config = $addonConfig[$type] ?? $addonConfig['wechat'];        // SDK版本        $version = self::getSdkVersion();        if (isset($config['cert_client']) && substr($config['cert_client'], 0, 8) == '/addons/') {            $config['cert_client'] = ROOT_PATH . str_replace('/', DS, substr($config['cert_client'], 1));        }        if (isset($config['cert_key']) && substr($config['cert_key'], 0, 8) == '/addons/') {            $config['cert_key'] = ROOT_PATH . str_replace('/', DS, substr($config['cert_key'], 1));        }        if (isset($config['app_cert_public_key']) && substr($config['app_cert_public_key'], 0, 8) == '/addons/') {            $config['app_cert_public_key'] = ROOT_PATH . str_replace('/', DS, substr($config['app_cert_public_key'], 1));        }        if (isset($config['alipay_root_cert']) && substr($config['alipay_root_cert'], 0, 8) == '/addons/') {            $config['alipay_root_cert'] = ROOT_PATH . str_replace('/', DS, substr($config['alipay_root_cert'], 1));        }        if (isset($config['ali_public_key']) && (Str::endsWith($config['ali_public_key'], '.crt') || Str::endsWith($config['ali_public_key'], '.pem'))) {            $config['ali_public_key'] = ROOT_PATH . str_replace('/', DS, substr($config['ali_public_key'], 1));        }        // V3支付        if (self::isVersionV3()) {            if ($type == 'wechat') {                $config['mp_app_id'] = $config['app_id'] ?? '';                $config['app_id'] = $config['appid'] ?? '';                $config['mini_app_id'] = $config['miniapp_id'] ?? '';                $config['combine_mch_id'] = $config['combine_mch_id'] ?? '';                $config['mch_secret_key'] = $config['key_v3'] ?? '';                $config['mch_secret_cert'] = $config['cert_key'];                $config['mch_public_cert_path'] = $config['cert_client'];                $config['sub_mp_app_id'] = $config['sub_appid'] ?? '';                $config['sub_app_id'] = $config['sub_app_id'] ?? '';                $config['sub_mini_app_id'] = $config['sub_miniapp_id'] ?? '';                $config['sub_mch_id'] = $config['sub_mch_id'] ?? '';            } elseif ($type == 'alipay') {                $config['app_secret_cert'] = $config['private_key'] ?? '';                $config['app_public_cert_path'] = $config['app_cert_public_key'] ?? '';                $config['alipay_public_cert_path'] = $config['ali_public_key'] ?? '';                $config['alipay_root_cert_path'] = $config['alipay_root_cert'] ?? '';                $config['service_provider_id'] = $config['pid'] ?? '';            }            $modeArr = ['normal' => 0, 'dev' => 1, 'service' => 2];            $config['mode'] = $modeArr[$config['mode']] ?? 0;        }        // 日志        if ($config['log']) {            $config['log'] = [                'enable' => true,                'file'   => LOG_PATH . 'epaylogs' . DS . $type . '-' . date("Y-m-d") . '.log',                'level'  => 'debug'            ];        } else {            $config['log'] = [                'enable' => false,            ];        }        // GuzzleHttp配置,可选        $config['http'] = [            'timeout'         => 10,            'connect_timeout' => 10,            // 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html)        ];        $config['notify_url'] = empty($config['notify_url']) ? addon_url('epay/api/notifyx', [], false) . '/type/' . $type : $config['notify_url'];        $config['notify_url'] = !preg_match("/^(http:\/\/|https:\/\/)/i", $config['notify_url']) ? request()->root(true) . $config['notify_url'] : $config['notify_url'];        $config['return_url'] = empty($config['return_url']) ? addon_url('epay/api/returnx', [], false) . '/type/' . $type : $config['return_url'];        $config['return_url'] = !preg_match("/^(http:\/\/|https:\/\/)/i", $config['return_url']) ? request()->root(true) . $config['return_url'] : $config['return_url'];        //合并自定义配置        $config = array_merge($config, $custom);        //v3版本时返回的结构不同        if (self::isVersionV3()) {            $config = [$type => ['default' => $config], 'logger' => $config['log'], 'http' => $config['http'], '_force' => true];        }        return $config;    }    /**     * 获取微信Openid     *     * @param array $custom 自定义配置信息     * @return mixed|string     */    public static function getOpenid($custom = [])    {        $openid = '';        $auth = Auth::instance();        if ($auth->isLogin()) {            $third = get_addon_info('third');            if ($third && $third['state']) {                $thirdInfo = Third::where('user_id', $auth->id)->where('platform', 'wechat')->where('apptype', 'mp')->find();                $openid = $thirdInfo ? $thirdInfo['openid'] : '';            }        }        if (!$openid) {            $openid = Session::get("openid");            //如果未传openid,则去读取openid            if (!$openid) {                $addonConfig = get_addon_config('epay');                $wechat = new Wechat($custom['app_id'] ?? $addonConfig['wechat']['app_id'], $custom['app_secret'] ?? $addonConfig['wechat']['app_secret']);                $openid = $wechat->getOpenid();            }        }        return $openid;    }    /**     * 获取SDK版本     * @return mixed|string     */    public static function getSdkVersion()    {        $addonConfig = get_addon_config('epay');        return $addonConfig['version'] ?? self::SDK_VERSION_V2;    }    /**     * 判断是否V2支付     * @return bool     */    public static function isVersionV2()    {        return self::getSdkVersion() === self::SDK_VERSION_V2;    }    /**     * 判断是否V3支付     * @return bool     */    public static function isVersionV3()    {        return self::getSdkVersion() === self::SDK_VERSION_V3;    }}
 |