<?php
namespace Payment;

use Payment\Common\BaseStrategy;
use Payment\Common\PayException;
use Payment\Helper\Cmb\BindCardHelper;
use Payment\Helper\Cmb\PubKeyHelper;

/**
 * 用于完成一些辅助操作，例如： 招商绑卡  操作
 * Class HelperContext
 * @package Payment
 */
class HelperContext
{
    /**
     * 转款渠道
     * @var BaseStrategy
     */
    protected $helper;

    /**
     * 设置对应的退款渠道
     * @param string $way 对应的方式渠道
     *  - @see Config
     *
     * @param array $config 配置文件
     * @throws PayException
     * @author helei
     */
    public function initHelper($way, array $config)
    {
        try {
            switch ($way) {
                case Config::CMB_BIND:
                    $this->helper = new BindCardHelper($config);
                    break;
                case Config::CMB_PUB_KEY:
                    $this->helper = new PubKeyHelper($config);
                    break;
                default:
                    throw new PayException('当前仅支持：CMB_BIND CMB_PUB_KEY 操作');
            }
        } catch (PayException $e) {
            throw $e;
        }
    }

    /**
     * 获取帮助操作
     *
     * @param array $data
     *
     * @return array
     * @throws PayException
     * @author helei
     */
    public function helper(array $data)
    {
        if (! $this->helper instanceof BaseStrategy) {
            throw new PayException('请检查初始化是否正确');
        }

        try {
            return $this->helper->handle($data);
        } catch (PayException $e) {
            throw $e;
        }
    }
}


<?php
namespace Payment\Utils;

/**
 * @author: helei
 * @createTime: 2016-06-08 16:29
 * @description: rsa加密算法
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 */
class RsaEncrypt
{
    protected $key;

    public function __construct($key)
    {
        $this->key = $key;
    }

    /**
     * 设置key
     * @param $key
     * @author helei
     */
    public function setKey($key)
    {
        $this->key = $key;
    }

    /**
     * RSA签名, 此处秘钥是私有秘钥
     * @param string $data 签名的数组
     * @throws \Exception
     * @return string
     * @author helei
     */
    public function encrypt($data)
    {
        if ($this->key === false) {
            return '';
        }

        $res = openssl_get_privatekey($this->key);
        if (empty($res)) {
            throw new \Exception('您使用的私钥格式错误，请检查RSA私钥配置');
        }

        openssl_sign($data, $sign, $res);
        openssl_free_key($res);

        //base64编码
        $sign = base64_encode($sign);
        return $sign;
    }

    /**
     * RSA解密 此处秘钥是用户私有秘钥
     * @param string $content 需要解密的内容，密文
     * @throws \Exception
     * @return string
     * @author helei
     */
    public function decrypt($content)
    {
        if ($this->key === false) {
            return '';
        }

        $res = openssl_get_privatekey($this->key);
        if (empty($res)) {
            throw new \Exception('您使用的私钥格式错误，请检查RSA私钥配置');
        }

        //用base64将内容还原成二进制
        $content = base64_decode($content);
        //把需要解密的内容，按128位拆开解密
        $result  = '';
        for ($i = 0; $i < strlen($content)/128; $i++) {
            $data = substr($content, $i * 128, 128);
            openssl_private_decrypt($data, $decrypt, $res);
            $result .= $decrypt;
        }
        openssl_free_key($res);
        return $result;
    }

    /**
     * RSA验签 ，此处的秘钥，是第三方公钥
     * @param string $data 待签名数据
     * @param string $sign 要校对的的签名结果
     * @throws \Exception
     * @return bool
     * @author helei
     */
    public function rsaVerify($data, $sign)
    {
        // 初始时，使用公钥key
        $res = openssl_get_publickey($this->key);
        if (empty($res)) {
            throw new \Exception('支付宝RSA公钥错误。请检查公钥文件格式是否正确');
        }

        $result = (bool)openssl_verify($data, base64_decode($sign), $res);
        openssl_free_key($res);
        return $result;
    }
}


<?php
namespace Payment\Common\Ali\Data;

use Payment\Common\PayException;

/**
 * Class RefundData
 *
 * @property string $trade_no 支付宝的订单号，优先使用
 * @property string $out_trade_no 商户系统内部的订单号
 * @property float $refund_fee 退款总金额，订单总金额，只能为整数
 * @property string $reason 	退款的原因说明
 * @property string $refund_no  商户系统内部的退款单号，商户系统内部唯一，同一退款单号多次请求只退一笔(3～24位)
 * @property string $operator_id 商户的操作员编号
 * @property string $terminal_id 商户机具终端编号
 * @property string $store_id  	商户门店编号
 *
 * @package Payment\Common\Ali\Data
 * anthor helei
 */
class RefundData extends AliBaseData
{

    /**
     * 检查退款数据是否正常
     * @author helei
     */
    protected function checkDataParam()
    {
        $tradeNo = $this->trade_no;// 支付宝交易号，查询效率高
        $outTradeNo = $this->out_trade_no;// 商户订单号，查询效率低，不建议使用
        $refundAmount = $this->refund_fee;

        // 二者不能同时为空
        if (empty($outTradeNo) && empty($tradeNo)) {
            throw new PayException('必须提供支付宝交易号或者商户网站唯一订单号。建议使用支付宝交易号');
        }

        if (empty($refundAmount) || ! is_numeric($refundAmount)) {
            throw new PayException('refund_fee 退款的金额，该金额不能大于订单金额,单位为元，支持两位小数');
        }
    }

    /**
     * 业务请求参数的集合，最大长度不限，除公共参数外所有请求参数都必须放在这个参数中传递
     *
     * @return string
     */
    protected function getBizContent()
    {
        $content = [
            'out_trade_no'    => $this->out_trade_no,
            'trade_no'        => $this->trade_no,
            'refund_amount'     => $this->refund_fee,
            'refund_reason'     => $this->reason,
            'out_request_no'    => $this->refund_no,
            'operator_id'       => $this->operator_id,
            'store_id'          => $this->store_id,
            'terminal_id'       => $this->terminal_id,
        ];

        return $content;
    }
}


<?php
namespace Payment\Query\Wx;

use Payment\Common\Weixin\Data\Query\RefundQueryData;
use Payment\Common\Weixin\WxBaseStrategy;
use Payment\Common\WxConfig;
use Payment\Config;

/**
 *
 * 微信退款订单查询
 * Class WxRefudnQuery
 * @package Payment\Query
 * anthor helei
 */
class WxRefundQuery extends WxBaseStrategy
{
    protected $reqUrl = 'https://api.mch.weixin.qq.com/{debug}/pay/refundquery';

    public function getBuildDataClass()
    {
        return RefundQueryData::class;
    }

    /**
     * 处理通知的返回数据
     * @param array $data
     * @return mixed
     * @author helei
     */
    protected function retData(array $data)
    {
        if ($this->config->returnRaw) {
            $data['channel'] = Config::WX_REFUND;
            return $data;
        }

        // 请求失败，可能是网络
        if ($data['return_code'] != 'SUCCESS') {
            return $retData = [
                'is_success'    => 'F',
                'error' => $data['return_msg'],
                'channel' => Config::WX_REFUND,
            ];
        }

        // 业务失败
        if ($data['result_code'] != 'SUCCESS') {
            return $retData = [
                'is_success'    => 'F',
                'error' => $data['err_code_des'],
                'channel' => Config::WX_REFUND,
            ];
        }

        // 正确
        return $this->createBackData($data);
    }

    /**
     * 返回数据给客户端
     * @param array $data
     * @return array
     * @author helei
     */
    protected function createBackData(array $data)
    {
        $refund_count = $data['refund_count'];// 退款的笔数

        // 将金额处理为元
        $totalFee = bcdiv($data['total_fee'], 100, 2);
        $refundFee = bcdiv($data['refund_fee'], 100, 2);

        // 获取退款笔数
        $refundData = [];
        for ($i = 0; $i<$refund_count; $i++) {
            $refund_no = 'out_refund_no_' . $i;// 商户退款单号
            $refund_id = 'refund_id_' . $i;// 微信退款单号
            $refund_channel = 'refund_channel_' . $i;// 退款渠道
            $refund_fee = 'refund_fee_' . $i;// 申请退款金额
            //$settlement_refund_fee = 'settlement_refund_fee_' . $i;// 实际退款金额
            $refund_status = 'refund_status_' . $i;// 退款状态
            $recv_accout = 'refund_recv_accout_' . $i;// 退款入账账户

            $fee = bcdiv($refund_fee, 100, 2);

            // 一笔订单可能被分为多笔,进行退款
            $refundData[] = [
                'refund_no' => $data[$refund_no],
                'refund_id' => $data[$refund_id],
                'refund_channel'    => $data[$refund_channel],
                'refund_fee'    => $fee,
                //'settlement_refund_fee' => $data[$settlement_refund_fee],
                'refund_status' => strtolower($data[$refund_status]),
                'recv_accout'   => $data[$recv_accout],
            ];
        }

        $retData = [
            'is_success'    => 'T',
            'response'  => [
                'amount'   => $totalFee,// 订单总金额
                'order_no'   => $data['out_trade_no'],// 商户订单号
                'transaction_id'   => $data['transaction_id'],// 微信订单号
                'refund_count' => $data['refund_count'],// 退款总笔数
                'refund_fee' => $refundFee,// 退款总金额
                'refund_data'   => $refundData,// 退款信息
                'channel' => Config::WX_REFUND,
            ],
        ];

        return $retData;
    }
}


<?php
namespace Payment\Charge\Ali;

use Payment\Common\Ali\AliBaseStrategy;
use Payment\Common\Ali\Data\Charge\QrChargeData;
use Payment\Common\AliConfig;
use Payment\Common\PayException;

/**
 * 支付宝扫码支付- 用户扫商户生成的二维码完成支付
 *
 * Class AliQrCharge
 * @package Payment\Charge\Weixin
 *
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 */
class AliQrCharge extends AliBaseStrategy
{
    protected $method = 'alipay.trade.precreate';

    public function getBuildDataClass()
    {
        $this->config->method = $this->method;
        return QrChargeData::class;
    }

    /**
     * 处理扫码支付的返回值
     * @param array $ret
     *
     * @throws PayException
     * @return string  可生产二维码的uri
     * @author helei
     */
    protected function retData(array $ret)
    {
        $url = parent::retData($ret);

        // 发起网络请求
        try {
            $data = $this->sendReq($url);
        } catch (PayException $e) {
            throw $e;
        }

        // 检查是否报错
        if ($data['code'] !== '10000') {
            throw new PayException($data['sub_msg']);
        }

        return $data['qr_code'];
    }
}


<?php
namespace Payment\Common\Ali\Data\Charge;

use Payment\Common\PayException;

/**
 * 支付宝 条码支付
 *  - 扫户扫用户的二维码，完成支付
 * Class BarChargeData
 * @package Payment\Common\Ali\Data\Charge
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 *
 * @property string $operator_id  商户操作员编号
 * @property string $terminal_id 商户机具终端编号
 * @property string $scene  条码支付，取值：bar_code 声波支付，取值：wave_code
 * @property string $auth_code 支付授权码 二维码的数值
 *
 */
class BarChargeData extends ChargeBaseData
{
    /**
     * 业务请求参数的集合，最大长度不限，除公共参数外所有请求参数都必须放在这个参数中传递
     *
     * @return string
     */
    protected function getBizContent()
    {
        $content = [
            'out_trade_no'  => strval($this->order_no),
            'scene' => $this->scene,
            'auth_code' => $this->auth_code,
            'product_code' => 'FACE_TO_FACE_PAYMENT',

            'subject'       => strval($this->subject),
            // TODO 支付宝用户ID
            // 'seller_id' => $this->partner,

            'body'          => strval($this->body),
            'total_amount'  => strval($this->amount),
            // TODO 折扣金额
            // 'discountable_amount' => '',
            // TODO  业务扩展参数 订单商品列表信息，待支持
            // 'extend_params => '',
            // 'goods_detail' => '',

            'operator_id' => $this->operator_id,
            'store_id' => $this->store_id,
            'terminal_id' => $this->terminal_id,
        ];

        $timeExpire = $this->timeout_express;
        if (! empty($timeExpire)) {
            $express = floor(($timeExpire - strtotime($this->timestamp)) / 60);
            ($express > 0) && $content['timeout_express'] = $express . 'm';// 超时时间 统一使用分钟计算
        }

        return $content;
    }

    protected function checkDataParam()
    {
        parent::checkDataParam(); // TODO: Change the autogenerated stub

        $scene = $this->scene;
        $authCode = $this->auth_code;

        if (empty($scene) || ! in_array($scene, ['bar_code', 'wave_code'])) {
            throw new PayException('支付场景 scene 必须设置 条码支付：bar_code 声波支付：wave_code');
        }

        if (empty($authCode)) {
            throw new PayException('请提供支付授权码');
        }
    }
}


<?php
namespace Payment\Refund;

use Payment\Common\Weixin\Data\RefundData;
use Payment\Common\Weixin\WxBaseStrategy;
use Payment\Config;

/**
 * Class WxRefund
 * 微信退款操作
 * @package Payment\Refund
 * anthor helei
 */
class WxRefund extends WxBaseStrategy
{
    protected $reqUrl = 'https://api.mch.weixin.qq.com/{debug}/secapi/pay/refund';

    public function getBuildDataClass()
    {
        return RefundData::class;
    }

    /**
     * 处理退款的返回数据
     * @param array $ret
     * @return mixed
     * @author helei
     */
    protected function retData(array $ret)
    {
        if ($this->config->returnRaw) {
            $ret['channel'] = Config::WX_REFUND;
            return $ret;
        }

        // 请求失败，可能是网络
        if ($ret['return_code'] != 'SUCCESS') {
            return $retData = [
                'is_success'    => 'F',
                'error' => $ret['return_msg']
            ];
        }

        // 业务失败
        if ($ret['result_code'] != 'SUCCESS') {
            return $retData = [
                'is_success'    => 'F',
                'error' => $ret['err_code_des']
            ];
        }

        return $this->createBackData($ret);
    }

    /**
     * 处理返回的数据
     * @param array $data
     * @return array
     * @author helei
     */
    protected function createBackData(array $data)
    {
        // 将订单总金额金额处理为元
        $total_fee = bcdiv($data['total_fee'], 100, 2);
        // 将订单退款金额处理为元
        $refund_fee = bcdiv($data['refund_fee'], 100, 2);

        $retData = [
            'is_success'    => 'T',
            'response'  => [
                'transaction_id'   => $data['transaction_id'],
                'order_no'  => $data['out_trade_no'],
                'refund_no' => $data['out_refund_no'],
                'refund_id' => $data['refund_id'],
                'refund_fee'    => $refund_fee,
                'refund_channel' => $data['refund_channel'],
                'amount'   => $total_fee,
                'channel'   => Config::WX_REFUND,

                'coupon_refund_fee' => bcdiv($data['coupon_refund_fee'], 100, 2),
                'coupon_refund_count' => $data['coupon_refund_count'],
                'cash_fee' => bcdiv($data['cash_fee'], 100, 2),
                'cash_refund_fee' => bcdiv($data['cash_refund_fee'], 100, 2),
            ],
        ];

        return $retData;
    }
}


<?php
namespace Payment\Notify;

use Payment\Common\PayException;
use Payment\Common\WxConfig;
use Payment\Config;
use Payment\Utils\ArrayUtil;
use Payment\Utils\DataParser;

/**
 * Class WxNotify
 * 微信回调处理
 * @package Payment\Notify
 * anthor helei
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 *
 */
class WxNotify extends NotifyStrategy
{

    /**
     * WxNotify constructor.
     * @param array $config
     * @throws PayException
     */
    public function __construct(array $config)
    {
        try {
            $this->config = new WxConfig($config);
        } catch (PayException $e) {
            throw $e;
        }
    }

    /**
     * 获取微信返回的异步通知数据
     * @return array|bool
     * @author helei
     */
    public function getNotifyData()
    {
        // php://input 带来的内存压力更小
        $data = @file_get_contents('php://input');// 等同于微信提供的：$GLOBALS['HTTP_RAW_POST_DATA']
        // 将xml数据格式化为数组
        $arrData = DataParser::toArray($data);
        if (empty($arrData)) {
            return false;
        }

        // 移除值中的空格  xml转化为数组时，CDATA 数据会被带入额外的空格。
        $arrData = ArrayUtil::paraFilter($arrData);

        return $arrData;
    }

    /**
     * 检查微信异步通知的数据是否正确
     * @param array $data
     *
     * @author helei
     * @return boolean
     */
    public function checkNotifyData(array $data)
    {
        if ($data['return_code'] != 'SUCCESS' || $data['result_code'] != 'SUCCESS') {
            // $arrData['return_msg']  返回信息，如非空，为错误原因
            // $data['result_code'] != 'SUCCESS'  表示业务失败
            return false;
        }

        // 检查返回数据签名是否正确
        return $this->verifySign($data);
    }

    /**
     * 检查微信返回的数据是否被篡改过
     * @param array $retData
     * @return boolean
     * @author helei
     */
    protected function verifySign(array $retData)
    {
        $retSign = $retData['sign'];
        $values = ArrayUtil::removeKeys($retData, ['sign', 'sign_type']);

        $values = ArrayUtil::paraFilter($values);

        $values = ArrayUtil::arraySort($values);

        $signStr = ArrayUtil::createLinkstring($values);

        $signStr .= '&key=' . $this->config->md5Key;
        switch ($this->config->signType) {
            case 'MD5':
                $sign = md5($signStr);
                break;
            case 'HMAC-SHA256':
                $sign = hash_hmac('sha256', $signStr, $this->config->md5Key);
                break;
            default:
                $sign = '';
        }

        return strtoupper($sign) === $retSign;
    }

    /**
     * 获取向客户端返回的数据
     * @param array $data
     *
     * @return array
     * @author helei
     */
    protected function getRetData(array $data)
    {
        if ($this->config->returnRaw) {
            $data['channel'] = Config::WX_CHARGE;
            return $data;
        }

        // 将金额处理为元
        $totalFee = bcdiv($data['total_fee'], 100, 2);
        $cashFee = bcdiv($data['cash_fee'], 100, 2);

        $retData = [
            'bank_type' => $data['bank_type'],
            'cash_fee' => $cashFee,
            'device_info' => $data['device_info'],
            'fee_type' => $data['fee_type'],
            'is_subscribe' => $data['is_subscribe'],
            'buyer_id'   => $data['openid'],
            'order_no'   => $data['out_trade_no'],
            'pay_time'   => date('Y-m-d H:i:s', strtotime($data['time_end'])),// 支付完成时间
            'amount'   => $totalFee,
            'trade_type' => $data['trade_type'],
            'transaction_id'   => $data['transaction_id'],
            'trade_state'   => strtolower($data['return_code']),
            'channel'   => Config::WX_CHARGE,
        ];

        // 检查是否存在用户自定义参数
        if (isset($data['attach']) && ! empty($data['attach'])) {
            $retData['return_param'] = $data['attach'];
        }

        return $retData;
    }

    /**
     * 处理完后返回的数据格式
     * @param bool $flag
     * @param string $msg 通知信息，错误原因
     * @author helei
     * @return string
     */
    protected function replyNotify($flag, $msg = 'OK')
    {
        // 默认为成功
        $result = [
            'return_code'   => 'SUCCESS',
            'return_msg'    => 'OK',
        ];
        if (! $flag) {
            // 失败
            $result = [
                'return_code'   => 'FAIL',
                'return_msg'    => $msg,
            ];
        }

        return DataParser::toXml($result);
    }
}


<?php
namespace Payment\Charge\Wx;

use Payment\Common\Weixin\Data\Charge\BarChargeData;
use Payment\Common\Weixin\WxBaseStrategy;
use Payment\Common\WxConfig;

/**
 * @author: helei
 * @createTime: 2017-03-06 18:29
 * @description: 微信 刷卡支付  对应支付宝的条码支付
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 */
class WxBarCharge extends WxBaseStrategy
{
    protected $reqUrl = 'https://api.mch.weixin.qq.com/{debug}/pay/micropay';

    public function getBuildDataClass()
    {
        return BarChargeData::class;
    }

    /**
     * 返回的数据
     * @param array $ret
     * @return array
     */
    protected function retData(array $ret)
    {
        $ret['total_fee'] = bcdiv($ret['total_fee'], 100, 2);
        $ret['cash_fee'] = bcdiv($ret['cash_fee'], 100, 2);

        if ($this->config->returnRaw) {
            return $ret;
        }

        return $ret;
    }
}


<?php
namespace Payment\Charge\Ali;

use Payment\Common\Ali\AliBaseStrategy;
use Payment\Common\Ali\Data\Charge\WebChargeData;

/**
 * @author: helei
 * @createTime: 2016-07-14 17:56
 * @description: 支付宝 即时到账 接口
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 *
 * Class AliWebCharge
 * @package Payment\Charge\Ali
 */
class AliWebCharge extends AliBaseStrategy
{
    // web 支付接口名称
    protected $method = 'alipay.trade.page.pay';

    /**
     * 获取支付对应的数据完成类
     * @return string
     * @author helei
     */
    public function getBuildDataClass()
    {
        $this->config->method = $this->method;
        // 以下两种方式均可以
        return WebChargeData::class;
        //return 'Payment\Common\Ali\Data\Charge\WebChargeData';
    }

    /**
     * 返回可发起h5支付的请求
     * @param array $data
     * @return array|string
     */
    protected function retData(array $data)
    {
        $data = parent::retData($data);

        return $this->config->getewayUrl . '?' . http_build_query($data);
    }
}


<?php
namespace Payment\Common\Weixin\Data\Charge;

use Payment\Common\PayException;
use Payment\Utils\ArrayUtil;

/**
 * Class WebChargeData
 *
 * @inheritdoc
 * @property string $auth_code  扫码支付授权码，设备读取用户微信中的条码或者二维码信息
 *
 * @package Payment\Common\Weixin\Data\Charge
 */
class BarChargeData extends ChargeBaseData
{
    protected function checkDataParam()
    {
        parent::checkDataParam(); // TODO: Change the autogenerated stub

        // 刷卡支付,必须设置auth_code
        $authCode = $this->auth_code;
        if (empty($authCode)) {
            throw new PayException('扫码支付授权码,必须设置该参数.');
        }
    }

    /**
     * 生成下单的数据
     */
    protected function buildData()
    {
        $info = $this->scene_info;
        $sceneInfo = [];
        if ($info && is_array($info)) {
            $sceneInfo['store_info'] = $info;
        }

        $signData = [
            'appid' => trim($this->appId),
            'mch_id'    => trim($this->mchId),
            'device_info'   => $this->terminal_id,
            'nonce_str' => $this->nonceStr,
            'sign_type' => $this->signType,
            'body'  => trim($this->subject),
            //'detail' => json_encode($this->body, JSON_UNESCAPED_UNICODE),
            'attach'    => trim($this->return_param),
            'out_trade_no'  => trim($this->order_no),
            'total_fee' => $this->amount,
            'fee_type'  => $this->feeType,
            'spbill_create_ip'  => trim($this->client_ip),
            //'goods_tag' => '订单优惠标记',
            'limit_pay' => $this->limitPay,  // 指定不使用信用卡
            'auth_code'    => $this->auth_code,
            'scene_info' => $sceneInfo ? json_encode($sceneInfo, JSON_UNESCAPED_UNICODE) : '',

            // 服务商
            'sub_appid' => $this->sub_appid,
            'sub_mch_id' => $this->sub_mch_id,
        ];

        // 移除数组中的空值
        $this->retData = ArrayUtil::paraFilter($signData);
    }
}


<?php
namespace Payment\Common;

use Payment\Config;
use Payment\Utils\ArrayUtil;

/**
 * @author: helei
 * @createTime: 2016-07-28 18:05
 * @description: 支付相关接口的数据基类
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 *
 * Class BaseData
 * 支付相关接口的数据基类
 * @package Payment\Common\Weixin\Dataa
 *
 * @property string $limitPay   用户不可用指定渠道支付
 * @property boolean $returnRaw  是否返回原始数据，只进行签名检查
 * @property string $useSandbox 是否使用的测试模式
 * @property string $signType  签名算法
 *
 */
abstract class BaseData
{

    /**
     * 支付的请求数据
     * @var array $data
     */
    protected $data;

    /**
     * 支付返回的数据
     * @var array $retData
     */
    protected $retData;

    /**
     * 配置类型
     * @var string $configType
     */
    protected $channel;

    /**
     * BaseData constructor.
     * @param ConfigInterface $config
     * @param array $reqData
     * @throws PayException
     */
    public function __construct(ConfigInterface $config, array $reqData)
    {
        if ($config instanceof WxConfig) {
            $this->channel = Config::WECHAT_PAY;
        } elseif ($config instanceof AliConfig) {
            $this->channel = Config::ALI_PAY;
        } elseif ($config instanceof CmbConfig) {
            $this->channel = Config::CMB_PAY;
        } elseif ($config instanceof CcbConfig) {
            $this->channel = Config::CCB_PAY;
        }
        $this->data = array_merge($config->toArray(), $reqData);

        try {
            $this->checkDataParam();
        } catch (PayException $e) {
            throw $e;
        }
    }

    /**
     * 获取变量，通过魔术方法
     * @param string $name
     * @return null|string
     * @author helei
     */
    public function __get($name)
    {
        if (isset($this->data[$name])) {
            return $this->data[$name];
        }

        return null;
    }

    /**
     * 设置变量
     * @param $name
     * @param $value
     * @author helei
     */
    public function __set($name, $value)
    {
        $this->data[$name] = $value;
    }

    /**
     * 设置签名
     * @author helei
     */
    public function setSign()
    {
        $this->buildData();

        if ($this->channel === Config::CMB_PAY) {
            $data = $this->retData['reqData'];
        } else {
            $data = $this->retData;
        }
        $values = ArrayUtil::removeKeys($data, ['sign']);

        $values = ArrayUtil::arraySort($values);

        $signStr = ArrayUtil::createLinkstring($values);

        $this->retData['sign'] = $this->makeSign($signStr);
    }

    /**
     * 返回处理之后的数据
     * @return array
     * @author helei
     */
    public function getData()
    {
        return $this->retData;
    }

    /**
     * 签名算法实现  便于后期扩展微信不同的加密方式
     * @param string $signStr
     * @return string
     */
    abstract protected function makeSign($signStr);

    /**
     * 构建用于支付的签名相关数据
     * @return array
     */
    abstract protected function buildData();

    /**
     * 检查传入的参数. $reqData是否正确.
     * @return mixed
     * @throws PayException
     */
    abstract protected function checkDataParam();
}


<?php
namespace Payment\Common\Weixin;

use GuzzleHttp\Client;
use Payment\Common\PayException;
use Payment\Common\Weixin\Data\WxBaseData;
use Payment\Common\WxConfig;
use Payment\Utils\Curl;
use Payment\Utils\DataParser;
use Payment\Utils\StrUtil;

/**
 * 微信支付沙盒测试
 * Class WechatHelper
 * @package Payment\Common\Weixin
 */
class WechatHelper extends WxBaseData
{
    // 沙盒测试url
    const SANDBOX_URL = 'https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey';

    /**
     * 获取沙盒的签名信息
     * @return mixed
     * @throws PayException
     */
    public function getSandboxSignKey()
    {
        $this->setSign();

        $xml = DataParser::toXml($this->getData());

        $url = self::SANDBOX_URL;

        $client = new Client([
            'timeout' => '10.0'
        ]);
        $options = [
            'body' => $xml,
            'http_errors' => false
        ];

        $response = $client->request('POST', self::SANDBOX_URL, $options);

        if ($response->getStatusCode() != '200') {
            throw new PayException('网络发生错误，请稍后再试curl返回码：' . $response->getReasonPhrase());
        }
        // 格式化为数组
        $retData = DataParser::toArray($response->getBody()->getContents());
        if (strtoupper($retData['return_code']) != 'SUCCESS') {
            throw new PayException('微信返回错误提示:' . $retData['retmsg']);
        }

        return $retData['sandbox_signkey'];
    }

    /**
     * 构建请求的数据
     */
    protected function buildData()
    {
        $this->retData = [
            'mch_id'    => $this->mchId,
            'nonce_str' => StrUtil::getNonceStr(),
        ];
    }

    protected function checkDataParam()
    {
        // TODO: Implement checkDataParam() method.
    }
}


<?php
/**
 * @author: helei
 * @createTime: 2016-08-03 15:14
 * @description:
 */

namespace Payment\Common\Weixin\Data;

use Payment\Common\PayException;
use Payment\Common\WxConfig;
use Payment\Utils\ArrayUtil;

/**
 * 用户退款
 * Class RefundData
 *
 * @property string $refund_no  商户系统内部的退款单号，商户系统内部唯一，同一退款单号多次请求只退一笔
 * @property string $transaction_id  微信生成的订单号，在支付通知中有返回
 * @property string $out_trade_no  商户侧传给微信的订单号
 * @property int $total_fee 订单总金额，单位为分，只能为整数
 * @property int $refund_fee 退款总金额，订单总金额，单位为分，只能为整数
 * @property string $operator_id 操作员帐号, 默认为商户号
 * @property string $refund_account  退款账户  UNSETTLED:未结算资金退款  RECHARGE：可用余额退款
 *
 * @package Payment\Common\Weixin\Data
 * anthor helei
 */
class RefundData extends WxBaseData
{
    protected function buildData()
    {
        $this->retData = [
            'appid' => $this->appId,
            'mch_id'    => $this->mchId,
            'device_info' => $this->terminal_id,
            'nonce_str' => $this->nonceStr,
            'refund_fee_type' => $this->feeType,
            'transaction_id'    => $this->transaction_id,
            'out_trade_no' => $this->out_trade_no,
            'out_refund_no'  => $this->refund_no,// 商户退款单号
            'total_fee' => $this->total_fee,// 订单总金额
            'refund_fee' => $this->refund_fee,// 退款总金额
            'op_user_id'    => $this->operator_id,//操作员帐号, 默认为商户号
            'refund_account' => $this->refund_account,// 退款账户类型

            // 服务商
            'sub_appid' => $this->sub_appid,
            'sub_mch_id' => $this->sub_mch_id,
        ];

        $this->retData = ArrayUtil::paraFilter($this->retData);
    }

    /**
     * 检查参数
     * @author helei
     */
    protected function checkDataParam()
    {
        $refundNo = $this->refund_no;// 商户退款单号
        $transactionId = $this->transaction_id;
        $outTradeNo = $this->out_trade_no;
        $totalFee = $this->total_fee;
        $refundFee = $this->refund_fee;
        $operatorId = $this->operator_id;
        $refundAccount = $this->refund_account;// 退款账户

        if (empty($refundNo)) {
            throw new PayException('请设置退款单号 refund_no');
        }

        // 二者不能同时为空
        if (empty($transactionId) && empty($outTradeNo)) {
            throw new PayException('必须提供微信交易号或商户网站唯一订单号。建议使用微信交易号');
        }

        $this->total_fee = bcmul($totalFee, 100, 0);// 微信以分为单位
        $this->refund_fee = bcmul($refundFee, 100, 0);

        if (bccomp($refundFee, $totalFee, 2) === 1) {
            throw new PayException('退款金额不能大于订单总金额');
        }

        if (! in_array($refundAccount, [WxConfig::REFUND_RECHARGE, WxConfig::REFUND_UNSETTLED])) {
            $this->refund_account = WxConfig::REFUND_UNSETTLED;
        }

        // 该接口，微信配置文件，必须提供cert  key  两个pem文件
        $certPath = $this->appCertPem;
        $keyPath = $this->appKeyPem;
        if (empty($certPath)) {
            throw new PayException('退款接口，必须提供 apiclient_cert.pem 证书');
        }

        if (empty($keyPath)) {
            throw new PayException('退款接口，必须提供 apiclient_key.pem 证书');
        }

        if (empty($operatorId)) {
            $this->operator_id = $this->mchId;
        }
    }
}


<?php
namespace Payment\Refund;

use Payment\Common\Ali\AliBaseStrategy;
use Payment\Common\Ali\Data\RefundData;
use Payment\Common\PayException;
use Payment\Config;
use Payment\Utils\ArrayUtil;

/**
 * 退款业务
 * Class AliRefund
 * @package Payment\Refund
 */
class AliRefund extends AliBaseStrategy
{
    protected $method = 'alipay.trade.refund';

    /**
     * 返回退款数据构造类
     * @return string
     */
    public function getBuildDataClass()
    {
        $this->config->method = $this->method;
        return RefundData::class;
    }

    /**
     * 返回数据
     * @param array $data
     * @return array|string
     * @throws PayException
     */
    protected function retData(array $data)
    {
        $reqData = parent::retData($data);

        try {
            $ret = $this->sendReq($reqData);
        } catch (PayException $e) {
            throw $e;
        }
        $content = \GuzzleHttp\json_decode($data['biz_content'], true);
        $refundNo = $content['out_request_no'];

        if ($this->config->returnRaw) {
            $ret['channel'] = Config::ALI_REFUND;
            $ret['refund_no'] = $refundNo;
            return $ret;
        }

        if ($ret['code'] !== '10000') {
            return [
                'is_success'    => 'F',
                'error' => $ret['sub_msg'],
                'refund_no' => $refundNo
            ];
        }

        $retData = [
            'is_success'    => 'T',
            'response'  => [
                'transaction_id'   => $ret['trade_no'],
                'order_no'  => $ret['out_trade_no'],
                'logon_id'   => $ret['buyer_logon_id'],
                'fund_change' => $ret['fund_change'],// 本次退款是否发生了资金变化
                'refund_fee'    => $ret['refund_fee'],// 返回的总金额，这里支付宝会累计
                'refund_time' => $ret['gmt_refund_pay'],
                'refund_detail_item_list' => ArrayUtil::get($ret, 'refund_detail_item_list'),// 退款使用的资金渠道
                'refund_no' => $refundNo,
                'channel'   => Config::ALI_REFUND,
                'buyer_id'   => $ret['buyer_user_id'],
                'store_name' => ArrayUtil::get($ret, 'store_name'),
            ],
        ];

        return $retData;
    }
}


<?php
namespace Payment\Common;

use Payment\Common\Weixin\WechatHelper;
use Payment\Utils\ArrayUtil;
use Payment\Utils\StrUtil;

/**
 * @author: helei
 * @createTime: 2016-07-15 14:56
 * @description: 微信配置文件
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 */
final class WxConfig extends ConfigInterface
{
    // 微信分配的公众账号ID
    public $appId;

    // 微信支付分配的商户号
    public $mchId;

    // 随机字符串，不长于32位
    public $nonceStr;

    // 符合ISO 4217标准的三位字母代码
    public $feeType = 'CNY';

    // 交易开始时间 格式为yyyyMMddHHmmss
    public $timeStart;

    // 用于加密的md5Key
    public $md5Key;

    // 安全证书的路径
    public $cacertPath;

    // cert证书路径或者内容
    public $appCertPem;

    // key文件路径或者内容
    public $appKeyPem;

    // 	支付类型
    public $tradeType;

    // 指定回调页面
    public $returnUrl;

    // 是否开启服务商模式
    public $submch;

    // 关闭订单url  尚未接入
    const CLOSE_URL = 'https://api.mch.weixin.qq.com/{debug}/pay/closeorder';

    // 短连接转化url  尚未接入
    const SHORT_URL = 'https://api.mch.weixin.qq.com/{debug}/tools/shorturl';

    // 退款账户
    const REFUND_UNSETTLED = 'REFUND_SOURCE_UNSETTLED_FUNDS';// 未结算资金退款（默认使用未结算资金退款）
    const REFUND_RECHARGE = 'REFUND_SOURCE_RECHARGE_FUNDS';// 可用余额退款(限非当日交易订单的退款）

    // 沙箱测试相关
    const SANDBOX_PRE = 'sandboxnew';

    /**
     * 初始化配置文件参数
     * @param array $config
     * @throws PayException
     */
    protected function initConfig(array $config)
    {
        $basePath = dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'CacertFile' . DIRECTORY_SEPARATOR;
        $this->cacertPath = "{$basePath}wx_cacert.pem";

        $config = ArrayUtil::paraFilter($config);

        // 检查 微信分配的公众账号ID
        if (key_exists('app_id', $config) && !empty($config['app_id'])) {
            $this->appId = $config['app_id'];
        } else {
            throw new PayException('必须提供微信分配的公众账号ID');
        }

        // 检查 微信支付分配的商户号
        if (key_exists('mch_id', $config) && !empty($config['mch_id'])) {
            $this->mchId = $config['mch_id'];
        } else {
            throw new PayException('必须提供微信支付分配的商户号');
        }

        // 检查 异步通知的url
        if (key_exists('notify_url', $config) && !empty($config['notify_url'])) {
            $this->notifyUrl = trim($config['notify_url']);
        } else {
            throw new PayException('异步通知的url必须提供.');
        }

        // 设置交易开始时间 格式为yyyyMMddHHmmss   .再次之前一定要设置时区
        $startTime = time();
        $this->timeStart = date('YmdHis', $startTime);

        // 初始 MD5 key
        if (key_exists('md5_key', $config) && !empty($config['md5_key'])) {
            $this->md5Key = $config['md5_key'];
        } else {
            throw new PayException('MD5 Key 不能为空，再微信商户后台可查看');
        }

        // 设置支付的货币类型
        if (key_exists('fee_type', $config) && in_array($config['fee_type'], ['CNY'])) {
            $this->feeType = $config['fee_type'];
        }

        // 设置禁止使用的支付方式
        if (key_exists('limit_pay', $config) && !empty($config['limit_pay']) && $config['limit_pay'][0] === 'no_credit') {
            $this->limitPay = $config['limit_pay'][0];
        }

        if (key_exists('return_raw', $config)) {
            $this->returnRaw = filter_var($config['return_raw'], FILTER_VALIDATE_BOOLEAN);
        }

        if (key_exists('redirect_url', $config)) {
            $this->returnUrl = $config['redirect_url'];
        }

        // 以下两个文件，如果是调用资金流向接口，必须提供
        if (! empty($config['app_cert_pem'])) {
            $this->appCertPem = $config['app_cert_pem'];
        }
        if (! empty($config['app_key_pem'])) {
            $this->appKeyPem = $config['app_key_pem'];
        }

        if (key_exists('sign_type', $config) && in_array($config['sign_type'], ['MD5', 'HMAC-SHA256'])) {
            $this->signType = $config['sign_type'];
        } else {
            $this->signType = 'MD5';
        }

        // 生成随机字符串
        $this->nonceStr = StrUtil::getNonceStr();

        if (isset($config['use_sandbox']) && $config['use_sandbox'] === true) {
            $this->useSandbox = true;// 是沙箱模式  重新获取key
            $this->signType = 'MD5';// 沙箱模式只能使用 md5 。沙箱下，微信部分接口不支持 HMAC-SHA256

            $helper = new WechatHelper($this, []);
            $this->md5Key = $helper->getSandboxSignKey();
        } else {
            $this->useSandbox = false;// 不是沙箱模式
        }

        //2018-04-10 zj
        if(isset($config['submch']) && $config['submch'] === true){
            $this->submch = true;
        }else{
            $this->submch = false;
        }

    }
}


<?php
namespace Payment;

use Payment\Common\BaseStrategy;
use Payment\Common\PayException;
use Payment\Refund\AliRefund;
use Payment\Refund\CmbRefund;
use Payment\Refund\WxRefund;

/**
 * 退款上下文
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 *
 * Class RefundContext
 * @package Payment
 */
class RefundContext
{
    /**
     * 退款的渠道
     * @var BaseStrategy
     */
    protected $refund;

    /**
     * 设置对应的退款渠道
     * @param string $channel 退款渠道
     *  - @see Config
     *
     * @param array $config 配置文件
     * @throws PayException
     * @author helei
     */
    public function initRefund($channel, array $config)
    {
        try {
            switch ($channel) {
                case Config::ALI_REFUND:
                    $this->refund = new AliRefund($config);
                    break;
                case Config::WX_REFUND:
                    $this->refund = new WxRefund($config);
                    break;
                case Config::CMB_REFUND:
                    $this->refund = new CmbRefund($config);
                    break;
                default:
                    throw new PayException('当前仅支持：ALI WEIXIN CMB');
            }
        } catch (PayException $e) {
            throw $e;
        }
    }

    /**
     * 通过环境类调用支付退款操作
     *
     * @param array $data
     *
     * @return array
     * @throws PayException
     * @author helei
     */
    public function refund(array $data)
    {
        if (! $this->refund instanceof BaseStrategy) {
            throw new PayException('请检查初始化是否正确');
        }

        try {
            return $this->refund->handle($data);
        } catch (PayException $e) {
            throw $e;
        }
    }
}


<?php
/**
 * @author: jingzhou
 * @createTime: 2018-03-03 15:14
 * @description: 微信红包
 */

namespace Payment\Common\Weixin\Data;

use Payment\Common\PayException;
use Payment\Utils\ArrayUtil;

/**
 * 微信红包
 * Class RedData
 *
 * @property string $mch_billno  商户系统内部的单号，商户系统内部唯一,接口根据商户订单号支持重入，如出现超时可再调用。
 * @property string $transaction_id  微信生成的订单号，在支付通知中有返回
 * @property string $out_trade_no  商户侧传给微信的订单号
 * @property string $re_openid 接受红包的用户用户在wxappid下的openid，服务商模式下可填入msgappid下的openid
 * @property int $total_amount 红包总金额，单位为分
 *
 * @property string $msgappid 服务商模式下触达用户时的appid(可填服务商自己的appid或子商户的appid)，服务商模式下必填，服务商模式下填入的子商户appid必须在微信支付商户平台中先录入，否则会校验不过。
 * @property int 发放红包使用场景，红包金额大于200时必传 PRODUCT_1:商品促销 PRODUCT_2:抽奖 PRODUCT_3:虚拟物品兑奖 PRODUCT_4:企业内部福利 PRODUCT_5:渠道分润 PRODUCT_6:保险回馈 PRODUCT_7:彩票派奖 PRODUCT_8:税务刮奖
 *
 * @package Payment\Common\Weixin\Data
 * anthor IT
 */
class RedData extends WxBaseData
{
    protected function buildData()
    {
        $this->retData = [
            'act_name'     =>   $this->act_name,//活动名称
            'client_ip'    =>   $this->client_ip,
            'mch_billno'   =>   $this->mch_billno,
            'mch_id'       =>   $this->mchId,
            'nonce_str'    =>   $this->nonceStr,
            're_openid'    =>   $this->re_openid,
            'remark'       =>   $this->remark,//备注信息
            'scene_id'     =>   $this->scene_id,//场景ID
            'send_name'    =>   $this->send_name,//红包发送者名称
            'total_amount' =>   $this->total_amount,//红包金额
            'total_num'    =>   $this->total_num ? $this->total_num : 1,//红包人数
            'wishing'      =>   $this->wishing,//祝福语
            'wxappid'      =>   $this->appId,

            // 服务商
            'msgappid'     =>   $this->sub_appid,
            'sub_mch_id'   =>   $this->sub_mch_id,
        ];
        $this->retData = ArrayUtil::paraFilter($this->retData);
    }

    /**
     * 检查参数
     * @author helei
     */
    protected function checkDataParam()
    {
        $mch_billno = $this->mch_billno;// 商户退款单号
        $sendName = $this->send_name;
        $reOpenid = $this->re_openid;
        $wishing = $this->wishing;
        $actName = $this->act_name;
        $remark = $this->remark;
        $total_amount = $this->total_amount;
        $sendListid = $this->send_listid;//红包订单的微信单号

        if (empty($mch_billno)) {
            throw new PayException('请设置单号 mch_billno');
        }
        if (empty($sendName)) {
            throw new PayException('请设置发送名称 send_name');
        }
        if (empty($reOpenid)) {
            throw new PayException('请设置接收者 openID');
        }
        if (empty($wishing)) {
            throw new PayException("请设置祝福语");
        }
        if (empty($actName)) {
            throw new PayException("请设置活动名称");
        }
        if (empty($remark)) {
            throw new PayException("请设置备注信息");
        }
        // 微信使用的单位位分.此处进行转化
        $this->total_amount = bcmul($total_amount, 100, 0);
        if (empty($total_amount) || $total_amount < 1) {
            throw new PayException('转账金额错误');
        }
        // 二者不能同时为空
        if (empty($sendListid) && empty($mch_billno)) {
            throw new PayException('必须提供微信交易号或商户网站唯一订单号。建议使用微信交易号');
        }

        // 该接口，微信配置文件，必须提供cert  key  两个pem文件
        $certPath = $this->appCertPem;
        $keyPath = $this->appKeyPem;
        if (empty($certPath)) {
            throw new PayException('红包接口，必须提供 apiclient_cert.pem 证书');
        }

        if (empty($keyPath)) {
            throw new PayException('红包接口，必须提供 apiclient_key.pem 证书');
        }

    }
}


<?php
namespace Payment\Query\Wx;

use Payment\Common\PayException;
use Payment\Common\Weixin\Data\Query\TransferQueryData;
use Payment\Common\Weixin\WxBaseStrategy;
use Payment\Config;
use Payment\Utils\DataParser;

/**
 * Class WxTransferQuery
 * @package Payment\Query
 * anthor helei
 */
class WxTransferQuery extends WxBaseStrategy
{
    protected $reqUrl = 'https://api.mch.weixin.qq.com/{debug}/mmpaymkttransfers/gettransferinfo';

    public function getBuildDataClass()
    {
        return TransferQueryData::class;
    }

    /**
     * 处理通知的返回数据
     * @param array $data
     * @return mixed
     * @author helei
     */
    protected function retData(array $data)
    {
        if ($this->config->returnRaw) {
            $data['channel'] = Config::WX_TRANSFER;
            return $data;
        }

        // 请求失败，可能是网络
        if ($data['return_code'] != 'SUCCESS') {
            return $retData = [
                'is_success'    => 'F',
                'error' => $data['return_msg'],
                'channel' => Config::WX_TRANSFER,
            ];
        }

        // 业务失败
        if ($data['result_code'] != 'SUCCESS') {
            return $retData = [
                'is_success'    => 'F',
                'error' => $data['err_code_des'],
                'channel' => Config::WX_TRANSFER,
            ];
        }

        // 正确
        return $this->createBackData($data);
    }

    /**
     * 返回数据给客户端
     * @param array $data
     * @return array
     * @author helei
     */
    protected function createBackData(array $data)
    {
        // 将金额处理为元
        $amount = bcdiv($data['payment_amount'], 100, 2);

        $retData = [
            'is_success'    => 'T',
            'response'  => [
                'trans_no'   => $data['partner_trade_no'],// 商户单号
                'transaction_id'  => $data['detail_id'],// 付款单号
                'status'  => strtolower($data['status']),// 转账状态
                'reason'    => $data['reason'],// 失败原因
                'openid'   => $data['openid'],
                'payee_name'   => $data['transfer_name'],// 收款用户姓名
                'amount'   => $amount,
                'pay_date'   => $data['transfer_time'],
                'desc'   => $data['desc'],// 付款描述
                'channel' => Config::WX_TRANSFER,
            ],
        ];

        return $retData;
    }

    /**
     * @param array $data
     * @author helei
     * @throws PayException
     * @return array|string
     */
    public function handle(array $data)
    {
        $buildClass = $this->getBuildDataClass();

        try {
            $this->reqData = new $buildClass($this->config, $data);
        } catch (PayException $e) {
            throw $e;
        }

        $this->reqData->setSign();

        $xml = DataParser::toXml($this->reqData->getData());

        try {
            $ret = $this->sendReq($xml);
        } catch (PayException $e) {
            throw $e;
        }

        return $this->retData($ret);
    }
}


<?php
namespace Payment\Trans;

use Payment\Common\Ali\AliBaseStrategy;
use Payment\Common\Ali\Data\TransData;
use Payment\Common\PayException;
use Payment\Config;

/**
 * 支付宝转账操作
 * Class AliTransfer
 * @package Payment\Trans
 */
class AliTransfer extends AliBaseStrategy
{
    protected $method = 'alipay.fund.trans.toaccount.transfer';

    public function getBuildDataClass()
    {
        $this->config->method = $this->method;
        return TransData::class;
    }

    protected function retData(array $data)
    {
        $reqData = parent::retData($data);

        try {
            $retData = $this->sendReq($reqData);
        } catch (PayException $e) {
            throw $e;
        }

        return $this->createBackData($retData);
    }

    /**
     * 处理返回的数据
     * @param array $data
     * @return array
     * @author helei
     */
    protected function createBackData(array $data)
    {
        if ($this->config->returnRaw) {
            $data['channel'] = Config::ALI_TRANSFER;
            return $data;
        }

        if ($data['code'] !== '10000') {
            return $retData = [
                'is_success'    => 'F',
                'error' => $data['sub_msg'],
                'channel' => Config::ALI_TRANSFER,
            ];
        }

        $retData = [
            'is_success'    => 'T',
            'response'  => [
                'trans_no'  => $data['out_biz_no'],// 商户转账唯一订单号
                'transaction_id'   => $data['order_id'],// 支付宝转账单据号
                'pay_date'   => $data['pay_date'],// 支付时间
                'channel'   => Config::ALI_TRANSFER,
            ],
        ];

        return $retData;
    }
}


<?php
namespace Payment\Red;

use Payment\Common\Weixin\Data\RedData;
use Payment\Common\Weixin\WxBaseStrategy;
use Payment\Config;
use Payment\Utils\ArrayUtil;

/**
 * 微信红包接口
 * Class WxRed
 * @package Payment\Trans
 * anthor jingzhou
 */
class WxRed extends WxBaseStrategy
{
	protected $reqUrl = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack';

    public function getBuildDataClass()
    {
        return RedData::class;
    }

    /**
     * 普通红包的返回数据
     * @param array $ret
     * @return mixed
     */
    protected function retData(array $ret)
    {
        if ($this->config->returnRaw) {
            $ret['channel'] = Config::WX_RED;
            return $ret;
        }

        // 请求失败，可能是网络
        if ($ret['return_code'] != 'SUCCESS') {
            return $retData = [
                'is_success'    => 'F',
                'error' => $ret['return_msg'],
                'channel'   => Config::WX_RED,
            ];
        }

        // 业务失败
        if ($ret['result_code'] != 'SUCCESS') {
            return $retData = [
                'is_success'    => 'F',
                'error' => $ret['err_code_des'],
                'channel'   => Config::WX_RED,
            ];
        }

        return $this->createBackData($ret);
    }

    /**
     * 返回数据
     * @param array $data
     * @return array
     */
    protected function createBackData(array $data)
    {
        $retData = [
            'is_success'    => 'T',
            'response'  => [
                'mch_billno'   => $data['mch_billno'],
                'send_listid'  => $data['send_listid'],
                'total_amount' => $data['total_amount'],
                're_openid'    => $data['re_openid'],
                'wxappid'      => $data['wxappid'],
                'mch_id'       => $data['mch_id'],
                'channel'   => Config::WX_RED,
            ],
        ];

        return $retData;
    }
    /**
     * 企业转账，不需要签名，使用返回true
     * @param array $retData
     * @return bool
     */
    protected function verifySign(array $retData)
    {
        return true;
    }
}


<?php
namespace Payment\Notify;

use Payment\Common\AliConfig;
use Payment\Common\PayException;
use Payment\Config;
use Payment\Utils\ArrayUtil;
use Payment\Utils\Rsa2Encrypt;
use Payment\Utils\RsaEncrypt;

/**
 * @author: helei
 * @createTime: 2016-07-20 16:21
 * @description: 支付宝回调通知
 *
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 */
class AliNotify extends NotifyStrategy
{
    /**
     * AliNotify constructor.
     * @param array $config
     * @throws PayException
     */
    public function __construct(array $config)
    {
        try {
            $this->config = new AliConfig($config);
        } catch (PayException $e) {
            throw $e;
        }
    }

    /**
     * 获取移除通知的数据  并进行简单处理（如：格式化为数组）
     *
     * 如果获取数据失败，返回false
     *
     * @return array|boolean
     * @author helei
     */
    public function getNotifyData()
    {
        $data = empty($_POST) ? $_GET : $_POST;
        if (empty($data) || ! is_array($data)) {
            return false;
        }

        return $data;
    }

    /**
     * 检查异步通知的数据是否合法
     *
     * 如果检查失败，返回false
     *
     * @param array $data  由 $this->getNotifyData() 返回的数据
     * @return boolean
     * @author helei
     */
    public function checkNotifyData(array $data)
    {
        $status = $this->getTradeStatus($data['trade_status']);
        if ($status !== Config::TRADE_STATUS_SUCC) {
            // 如果不是交易成功状态，直接返回错误，
            return false;
        }

        // 检查签名
        $flag = $this->verifySign($data);

        return $flag;
    }

    /**
     * 向客户端返回必要的数据
     * @param array $data 回调机构返回的回调通知数据
     * @return array|false
     * @author helei
     */
    protected function getRetData(array $data)
    {
        if ($this->config->returnRaw) {
            $data['channel'] = Config::ALI_CHARGE;
            return $data;
        }

        $retData = [
            'notify_time'   => ArrayUtil::get($data, 'notify_time'),
            'notify_type' => ArrayUtil::get($data, 'notify_type'),
            'notify_id' => ArrayUtil::get($data, 'notify_id'),
            'app_id' => ArrayUtil::get($data, 'app_id'),
            'transaction_id'   => ArrayUtil::get($data, 'trade_no'),
            'order_no'   => ArrayUtil::get($data, 'out_trade_no'),
            'out_biz_no' => ArrayUtil::get($data, 'out_biz_no'),
            'buyer_id'   => ArrayUtil::get($data, 'buyer_id'),
            'buyer_account' => ArrayUtil::get($data, 'buyer_logon_id'),
            'seller_id' => ArrayUtil::get($data, 'seller_id'),
            'seller_email' => ArrayUtil::get($data, 'seller_email'),
            'trade_state'   => $this->getTradeStatus($data['trade_status']),
            'amount'   => ArrayUtil::get($data, 'total_amount'),
            'receipt_amount' => ArrayUtil::get($data, 'receipt_amount'),// 商家在交易中实际收到的款项，单位为元
            'invoice_amount' => ArrayUtil::get($data, 'invoice_amount'),// 用户在交易中支付的可开发票的金额
            'pay_amount' => ArrayUtil::get($data, 'buyer_pay_amount'),// 用户在交易中支付的金额
            'point_amount' => ArrayUtil::get($data, 'point_amount'),// 使用集分宝支付的金额
            'refund_fee' => ArrayUtil::get($data, 'refund_fee'), // 总退款金额
            'subject'   => ArrayUtil::get($data, 'subject'),
            'body'   => ArrayUtil::get($data, 'body'),
            'trade_create_time' => ArrayUtil::get($data, 'gmt_create'),// 交易创建时间
            'pay_time'   => ArrayUtil::get($data, 'gmt_payment'),// 交易付款时间
            'trade_refund_time' => ArrayUtil::get($data, 'gmt_refund'), // 交易退款时间
            'trade_close_time' => ArrayUtil::get($data, 'gmt_close'), // 交易关闭时间
            'channel'   => Config::ALI_CHARGE,
        ];

        // 检查是否存在用户自定义参数
        if (isset($data['passback_params']) && ! empty($data['passback_params'])) {
            $retData['return_param'] = $data['passback_params'];
        }
        // 支付成功的各个渠道金额信息
        if (isset($data['fund_bill_list']) && ! empty($data['fund_bill_list'])) {
            $retData['fund_bill_list'] = \GuzzleHttp\json_decode($data['fund_bill_list'], true);
        }
        // 优惠券信息
        if (isset($data['voucher_detail_list']) && ! empty($data['voucher_detail_list'])) {
            $retData['voucher_detail_list'] = \GuzzleHttp\json_decode($data['voucher_detail_list'], true);
        }

        return $retData;
    }

    /**
     * 支付宝，成功返回 ‘success’   失败，返回 ‘fail’
     * @param boolean $flag 每次返回的bool值
     * @param string $msg 错误原因  后期考虑记录日志
     * @return string
     * @author helei
     */
    protected function replyNotify($flag, $msg = '')
    {
        if ($flag) {
            return 'success';
        } else {
            return 'fail';
        }
    }

    /**
     * 返回统一的交易状态
     * @param $status
     * @return string
     * @author helei
     */
    protected function getTradeStatus($status)
    {
        if (in_array($status, ['TRADE_SUCCESS', 'TRADE_FINISHED'])) {
            return Config::TRADE_STATUS_SUCC;
        } else {
            return Config::TRADE_STATUS_FAILD;
        }
    }

    /**
     * 检查支付宝数据 签名是否被篡改
     * @param array $data
     * @return boolean
     * @author helei
     */
    protected function verifySign(array $data)
    {
        $signType = strtoupper($data['sign_type']);
        $sign = $data['sign'];

        // 1. 剔除sign与sign_type参数
        $values = ArrayUtil::removeKeys($data, ['sign', 'sign_type']);
        //  2. 移除数组中的空值
        $values = ArrayUtil::paraFilter($values);
        // 3. 对待签名参数数组排序
        $values = ArrayUtil::arraySort($values);
        // 4. 将排序后的参数与其对应值，组合成“参数=参数值”的格式,用&字符连接起来
        $preStr = ArrayUtil::createLinkstring($values);

        if ($signType === 'RSA') {// 使用rsa方式
            $rsa = new RsaEncrypt($this->config->rsaAliPubKey);

            return $rsa->rsaVerify($preStr, $sign);
        } elseif ($signType === 'RSA2') {
            $rsa = new Rsa2Encrypt($this->config->rsaAliPubKey);

            return $rsa->rsaVerify($preStr, $sign);
        } else {
            return false;
        }
    }
}


<?php
namespace Payment\Common\Ali\Data\Charge;

use Payment\Common\Ali\Data\AliBaseData;
use Payment\Common\PayException;
use Payment\Config;
use Payment\Utils\ArrayUtil;

/**
 * @author: helei
 * @createTime: 2016-07-20 14:33
 * @description:
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 *
 * Class ChargeBaseData
 *
 * @inheritdoc
 *
 * @property string $body
 * @property string $subject
 * @property string $order_no
 * @property integer $timeout_express
 * @property string $amount
 * @property string $goods_type
 * @property string $return_param
 * @property string $store_id  	商户门店编号
 *
 * @package Payment\Common\Ali\Data\Charge
 * anthor helei
 */
abstract class ChargeBaseData extends AliBaseData
{

    /**
     * 检查传入的支付业务参数是否正确
     *
     * 如果输入参数不符合规范，直接抛出异常
     *
     * @author helei
     */
    protected function checkDataParam()
    {
        $subject = $this->subject;
        $orderNo = $this->order_no;
        $amount = $this->amount;
        $goodsType = $this->goods_type;
        $passBack = $this->return_param;

        // 检查 商品名称 与 商品描述
        if (empty($subject)) {
            throw new PayException('必须提供 商品的标题/交易标题/订单标题/订单关键字 等');
        }

        // 检查订单号是否合法
        if (empty($orderNo) || mb_strlen($orderNo) > 64) {
            throw new PayException('订单号不能为空，并且长度不能超过64位');
        }

        // 检查金额不能低于0.01
        if (bccomp($amount, Config::PAY_MIN_FEE, 2) === -1) {
            throw new PayException('支付金额不能低于 ' . Config::PAY_MIN_FEE . ' 元');
        }

        // 检查商品类型
        if (is_null($goodsType)) {// 默认为实物类商品
            $this->goods_type = 1;
        } elseif (! in_array($goodsType, [0 ,1])) {
            throw new PayException('商品类型可取值为：0-虚拟类商品  1-实物类商品');
        }

        // 返回参数进行urlencode编码
        if (! empty($passBack) && ! is_string($passBack)) {
            throw new PayException('回传参数必须是字符串');
        }
        if (!empty($passBack)) {
            $this->return_param = urlencode($passBack);
        }
    }
}


<?php
namespace Payment;

use Payment\Common\BaseStrategy;
use Payment\Common\PayException;
use Payment\Trans\AliTransfer;
use Payment\Trans\WxTransfer;

/**
 * 转账上下文
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 * Class TransferContext
 * @package Payment
 */
class TransferContext
{
    /**
     * 转款渠道
     * @var BaseStrategy
     */
    protected $transfer;

    /**
     * 设置对应的退款渠道
     * @param string $channel 退款渠道
     *  - @see Config
     *
     * @param array $config 配置文件
     * @throws PayException
     * @author helei
     */
    public function initTransfer($channel, array $config)
    {
        try {
            switch ($channel) {
                case Config::ALI_TRANSFER:
                    $this->transfer = new AliTransfer($config);
                    break;
                case Config::WX_TRANSFER:
                    $this->transfer = new WxTransfer($config);
                    break;
                default:
                    throw new PayException('当前仅支持：ALI WEIXIN两个常量');
            }
        } catch (PayException $e) {
            throw $e;
        }
    }

    /**
     * 通过环境类调用支付转款操作
     *
     * @param array $data
     *
     * @return array
     * @throws PayException
     * @author helei
     */
    public function transfer(array $data)
    {
        if (! $this->transfer instanceof BaseStrategy) {
            throw new PayException('请检查初始化是否正确');
        }

        try {
            return $this->transfer->handle($data);
        } catch (PayException $e) {
            throw $e;
        }
    }
}


<?php
namespace Payment\Charge\Wx;

use Payment\Common\Weixin\Data\BackAppChargeData;
use Payment\Common\Weixin\Data\Charge\AppChargeData;
use Payment\Common\Weixin\WxBaseStrategy;

/**
 * @author: helei
 * @createTime: 2016-07-14 17:56
 * @description: 微信 app 支付接口
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 */
class WxAppCharge extends WxBaseStrategy
{
    public function getBuildDataClass()
    {
        $this->config->tradeType = 'APP';
        return AppChargeData::class;
    }

    /**
     * 处理APP支付的返回值。直接返回与微信文档对应的字段
     * @param array $ret
     *
     * @return array $data
     *
     * ```php
     * $data = [
     *  'appid' => '',   // 应用ID
     *  'partnerid' => '',   // 商户号
     *  'prepayid'  => '',   // 预支付交易会话ID
     *  'package'   => '',  // 扩展字段  固定值：Sign=WXPay
     *  'noncestr'  => '',   // 随机字符串
     *  'timestamp' => '',   // 时间戳
     *  'sign'  => '',  // 签名
     * ];
     * ```
     * @author helei
     */
    protected function retData(array $ret)
    {
        $back = new BackAppChargeData($this->config, $ret);

        $back->setSign();
        $backData = $back->getData();

        return $backData;
    }
}


<?php
namespace Payment\Query\Ali;

use Payment\Common\Ali\AliBaseStrategy;
use Payment\Common\Ali\Data\Query\TransferQueryData;
use Payment\Common\PayException;
use Payment\Config;
use Payment\Utils\ArrayUtil;

/**
 * 查询转账订单的情况
 * Class AliTransferQuery
 * @package Payment\Query\Ali
 */
class AliTransferQuery extends AliBaseStrategy
{
    protected $method = 'alipay.fund.trans.order.query';

    public function getBuildDataClass()
    {
        $this->config->method = $this->method;
        return TransferQueryData::class;
    }

    protected function retData(array $data)
    {
        $reqData = parent::retData($data); // TODO: Change the autogenerated stub

        try {
            $ret = $this->sendReq($reqData);
        } catch (PayException $e) {
            throw $e;
        }

        if ($this->config->returnRaw) {
            $ret['channel'] = Config::ALI_TRANSFER;
            return $ret;
        }

        return $this->createBackData($ret);
    }

    /**
     * 返回数据给客户端  未完成，目前没有数据提供
     * @param array $data
     * @return array
     * @author helei
     */
    protected function createBackData(array $data)
    {
        if ($data['code'] !== '10000') {
            return $retData = [
                'is_success'    => 'F',
                'error' => $data['sub_msg'],
                'channel'   => Config::ALI_TRANSFER,
            ];
        }

        $retData = [
            'is_success'    => 'T',
            'response'  => [
                'transaction_id'   => ArrayUtil::get($data, 'order_id'),// 支付宝订单号
                'status'   => strtolower(ArrayUtil::get($data, 'status')),
                'pay_date' => ArrayUtil::get($data, 'pay_date'),// 转账日期
                'arrival_time_end' => ArrayUtil::get($data, 'arrival_time_end'),// 预计到账时间，转账到银行卡专用，格式为yyyy-MM-dd HH:mm:ss
                'amount'   => ArrayUtil::get($data, 'order_fee'),// 转账金额
                'trans_no'   => ArrayUtil::get($data, 'out_biz_no'),// 商户转账订单号
                'channel'   => Config::ALI_TRANSFER,
            ]
        ];
        if (isset($data['error_code'])) {
            $retData['response']['error_code'] = ArrayUtil::get($data, 'error_code');
            // 查询到的订单状态为FAIL失败或REFUND退票时，返回具体的原因。
            $retData['response']['fail_reason'] = ArrayUtil::get($data, 'fail_reason');
        }

        return $retData;
    }
}


<?php
namespace Payment\Utils;

/**
 * @author: helei
 * @createTime: 2016-06-08 13:02
 * @description: 数据解析的工具类，主要处理，xml到数组  数组到xml的转化
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 */
class DataParser
{
    /**
     * 输出xml字符
     * @param array $values
     * @return string|bool
     **/
    public static function toXml($values)
    {
        if (!is_array($values) || count($values) <= 0) {
            return false;
        }

        $xml = "<xml>";
        foreach ($values as $key => $val) {
            if (is_numeric($val)) {
                $xml.="<".$key.">".$val."</".$key.">";
            } else {
                $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
            }
        }
        $xml.="</xml>";
        return $xml;
    }

    /**
     * 将xml转为array
     * @param string $xml
     * @return array|false
     */
    public static function toArray($xml)
    {
        if (!$xml) {
            return false;
        }

        // 检查xml是否合法
        $xml_parser = xml_parser_create();
        if (!xml_parse($xml_parser, $xml, true)) {
            xml_parser_free($xml_parser);
            return false;
        }

        //将XML转为array
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);

        $data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);

        return $data;
    }

    /**
     * google api 二维码生成【QRcode可以存储最多4296个字母数字类型的任意文本，具体可以查看二维码数据格式】
     * @param string $text 二维码包含的信息，可以是数字、字符、二进制信息、汉字。不能混合数据类型，数据必须经过UTF-8 URL-encoded
     * @param string $widthHeight 生成二维码的尺寸设置
     * @param string $ecLevel 可选纠错级别，QR码支持四个等级纠错，用来恢复丢失的、读错的、模糊的、数据。
     *                            L-默认：可以识别已损失的7%的数据
     *                            M-可以识别已损失15%的数据
     *                            Q-可以识别已损失25%的数据
     *                            H-可以识别已损失30%的数据
     *
     * @param string $margin 生成的二维码离图片边框的距离
     *
     * @return string
     */
    public static function toQRimg($text, $widthHeight = '150', $ecLevel = 'L', $margin = '0')
    {
        $chl = urlencode($text);

        return "http://chart.apis.google.com/chart?chs={$widthHeight}x{$widthHeight}&cht=qr&chld={$ecLevel}|{$margin}&chl={$chl}";
    }
}


<?php
namespace Payment;

use Payment\Charge\Ali\AliAppCharge;
use Payment\Charge\Ali\AliBarCharge;
use Payment\Charge\Ali\AliWapCharge;
use Payment\Charge\Ali\AliWebCharge;
use Payment\Charge\Ali\AliQrCharge;
use Payment\Charge\Cmb\CmbCharge;
use Payment\Charge\Wx\WxAppCharge;
use Payment\Charge\Wx\WxBarCharge;
use Payment\Charge\Wx\WxPubCharge;
use Payment\Charge\Wx\WxQrCharge;
use Payment\Charge\Wx\WxWapCharge;
use Payment\Common\BaseStrategy;
use Payment\Common\PayException;

/**
 * @author: helei
 * @createTime: 2016-07-14 17:42
 * @description: 支付上下文
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 *
 * Class ChargeContext
 *
 * 支付的上下文类
 *
 * @package Payment
 */
class ChargeContext
{
    /**
     * 支付的渠道
     * @var BaseStrategy
     */
    protected $channel;

    /**
     * 设置对应的支付渠道
     * @param string $channel 支付渠道
     *  - @see Config
     * @param array $config 配置文件
     * @throws PayException
     * @author helei
     */
    public function initCharge($channel, array $config)
    {
        // 初始化时，可能抛出异常，再次统一再抛出给客户端进行处理
        try {
            switch ($channel) {
                case Config::ALI_CHANNEL_WAP:
                    $this->channel = new AliWapCharge($config);
                    break;
                case Config::ALI_CHANNEL_APP:
                    $this->channel = new AliAppCharge($config);
                    break;
                case Config::ALI_CHANNEL_WEB:
                    $this->channel = new AliWebCharge($config);
                    break;
                case Config::ALI_CHANNEL_QR:
                    $this->channel = new AliQrCharge($config);
                    break;
                case Config::ALI_CHANNEL_BAR:
                    $this->channel = new AliBarCharge($config);
                    break;

                case Config::WX_CHANNEL_APP:
                    $this->channel = new WxAppCharge($config);
                    break;
                case Config::WX_CHANNEL_LITE:// 小程序支付与公众号支付一样，仅仅是客户端的调用方式不同
                case Config::WX_CHANNEL_PUB:
                    $this->channel = new WxPubCharge($config);
                    break;
                case Config::WX_CHANNEL_WAP:
                    $this->channel = new WxWapCharge($config);
                    break;
                case Config::WX_CHANNEL_QR:
                    $this->channel = new WxQrCharge($config);
                    break;
                case Config::WX_CHANNEL_BAR:
                    $this->channel = new WxBarCharge($config);
                    break;

                case Config::CMB_CHANNEL_WAP:
                case Config::CMB_CHANNEL_APP:
                    $this->channel = new CmbCharge($config);
                    break;
                case Config::CCB_CHANNEL_WEB:
                    $this->channel = new CcbWebCharge($config);
                    break;
                default:
                    throw new PayException('当前仅支持：支付宝  微信 招商一网通 建设银行');
            }
        } catch (PayException $e) {
            throw $e;
        }
    }

    /**
     * 通过环境类调用支付
     * @param array $data
     *
     * ```php
     * $payData = [
     *      "order_no" => createPayid(),
     *      "amount" => '0.01',// 单位为元 ,最小为0.01
     *      "client_ip" => '127.0.0.1',
     *      "subject" => '测试支付',
     *      "body" => '支付接口测试',
     *      "extra_param"   => '',
     * ];
     * ```
     *
     * @return array
     * @throws PayException
     * @author helei
     */
    public function charge(array $data)
    {
        if (! $this->channel instanceof BaseStrategy) {
            throw new PayException('请检查初始化是否正确');
        }

        try {
            return $this->channel->handle($data);
        } catch (PayException $e) {
            throw $e;
        }
    }
}


<?php
namespace Payment;

use Payment\Common\BaseStrategy;
use Payment\Common\PayException;
use Payment\Query\Ali\AliChargeQuery;
use Payment\Query\Ali\AliRefundQuery;
use Payment\Query\Ali\AliTransferQuery;
use Payment\Query\Cmb\CmbChargeQuery;
use Payment\Query\Cmb\CmbRefundQuery;
use Payment\Query\Wx\WxChargeQuery;
use Payment\Query\Wx\WxRefundQuery;
use Payment\Query\Wx\WxTransferQuery;
use Payment\Query\Wx\WxRedQuery;

/**
 * 查询上下文
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 * Class QueryContext
 * @package Payment
 */
class QueryContext
{
    /**
     * 查询的渠道
     * @var BaseStrategy
     */
    protected $query;

    /**
     * 设置对应的查询渠道
     * @param string $channel 查询渠道
     *  - @see Config
     *
     * @param array $config 配置文件
     * @throws PayException
     * @author helei
     */
    public function initQuery($channel, array $config)
    {
        try {
            switch ($channel) {
                case Config::ALI_CHARGE:
                    $this->query = new AliChargeQuery($config);
                    break;
                case Config::ALI_REFUND:// 支付宝退款订单查询
                    $this->query = new AliRefundQuery($config);
                    break;
                case Config::ALI_TRANSFER:
                    $this->query = new AliTransferQuery($config);
                    break;
                case Config::WX_CHARGE:// 微信支付订单查询
                    $this->query = new WxChargeQuery($config);
                    break;
                case Config::WX_REFUND:// 微信退款订单查询
                    $this->query = new WxRefundQuery($config);
                    break;
                case Config::WX_TRANSFER:// 微信转款订单查询
                    $this->query = new WxTransferQuery($config);
                    break;
                case Config::WX_RED:// 微信红包查询 zj
                    $this->query = new WxRedQuery($config);
                    break;
                case Config::CMB_CHARGE:// 招商支付查询
                    $this->query = new CmbChargeQuery($config);
                    break;
                case Config::CMB_REFUND:// 招商退款查询
                    $this->query = new CmbRefundQuery($config);
                    break;
                default:
                    throw new PayException('当前仅支持：ALI_CHARGE ALI_REFUND WX_CHARGE WX_REFUND WX_TRANSFER WX_RED CMB_CHARGE CMB_REFUND');
            }
        } catch (PayException $e) {
            throw $e;
        }
    }

    /**
     * 通过环境类调用支付异步通知
     *
     * @param array $data
     *      // 二者设置一个即可
     *      $data => [
     *          'transaction_id'    => '原付款支付宝交易号',
     *          'order_no' => '商户订单号',
     *      ];
     *
     * @return array
     * @throws PayException
     * @author helei
     */
    public function query(array $data)
    {
        if (! $this->query instanceof BaseStrategy) {
            throw new PayException('请检查初始化是否正确');
        }

        try {
            return $this->query->handle($data);
        } catch (PayException $e) {
            throw $e;
        }
    }
}


<?php
namespace Payment\Client;

use Payment\RedContext;
use Payment\Common\PayException;
use Payment\Config;

/**
 * @author: IT
 * @createTime: 2018-03-07 18:20
 * @description: 红包
 * Class Red
 * @package Payment\Client
 *
 */
class Red
{
    private static $supportChannel = [
        Config::ALI_RED,// 支付宝红包

        Config::WX_RED,// 微信红包
    ];

    /**
     * 支付实例
     * @var RedContext
     */
    protected static $instance;

    protected static function getInstance($channel, $config)
    {
        /* 设置内部字符编码为 UTF-8 */
        mb_internal_encoding("UTF-8");

        if (is_null(self::$instance)) {
            static::$instance = new RedContext();
        }

        try {
            static::$instance->initRed($channel, $config);
        } catch (PayException $e) {
            throw $e;
        }

        return static::$instance;
    }

    /**
     * @param string $channel
     * @param array $config
     * @param array $metadata
     *
     * @return mixed
     * @throws PayException
     */
    public static function run($channel, $config, $metadata)
    {
        if (! in_array($channel, self::$supportChannel)) {
            throw new PayException('sdk当前不支持该支付渠道，当前仅支持：' . implode(',', self::$supportChannel));
        }

        try {
            $instance = self::getInstance($channel, $config);

            $ret = $instance->red($metadata);
        } catch (PayException $e) {
            throw $e;
        }

        return $ret;
    }
}


<?php
namespace Payment\Query\Wx;

use Payment\Common\Weixin\Data\Query\ChargeQueryData;
use Payment\Common\Weixin\WxBaseStrategy;
use Payment\Common\WxConfig;
use Payment\Config;
use Payment\Utils\ArrayUtil;

/**
 * 微信支付订单信息查询
 * Class WxChargeQuery
 * @package Payment\Query\Wx
 *
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 */
class WxChargeQuery extends WxBaseStrategy
{
    protected $reqUrl = 'https://api.mch.weixin.qq.com/{debug}/pay/orderquery';

    /**
     * 返回查询订单的数据
     * @author helei
     */
    public function getBuildDataClass()
    {
        return ChargeQueryData::class;
    }

    /**
     * 处理通知的返回数据
     * @param array $data
     * @return mixed
     * @author helei
     */
    protected function retData(array $data)
    {
        if ($this->config->returnRaw) {
            $data['channel'] = Config::WX_CHARGE;
            return $data;
        }

        // 请求失败，可能是网络
        if ($data['return_code'] != 'SUCCESS') {
            return $retData = [
                'is_success'    => 'F',
                'error' => $data['return_msg'],
                'channel'   => Config::WX_CHARGE,// 支付查询
            ];
        }

        // 业务失败
        if ($data['result_code'] != 'SUCCESS') {
            return $retData = [
                'is_success'    => 'F',
                'error' => $data['err_code_des'],
                'channel'   => Config::WX_CHARGE,// 支付查询
            ];
        }

        // 正确
        return $this->createBackData($data);
    }

    /**
     * 返回数据给客户端
     * @param array $data
     * @return array
     * @author helei
     */
    protected function createBackData(array $data)
    {
        // 将金额处理为元
        $totalFee = bcdiv($data['total_fee'], 100, 2);

        $retData = [
            'is_success'    => 'T',
            'response'  => [
                'amount'   => $totalFee,
                'channel'   => Config::WX_CHARGE,// 支付查询
                'order_no'   => $data['out_trade_no'],
                'buyer_id'   => $data['openid'],
                'trade_state'   => strtolower($data['trade_state']),
                'transaction_id'   => $data['transaction_id'],
                'time_end'   => date('Y-m-d H:i:s', strtotime($data['time_end'])),
                'return_param'    => $data['attach'],
                'terminal_id' => $data['device_info'],
                'trade_type' => $data['trade_type'],
                'bank_type' => $data['bank_type'],
                'trade_state_desc' => isset($data['trade_state_desc']) ? $data['trade_state_desc'] : '交易成功',
            ],
        ];

        return ArrayUtil::paraFilter($retData);
    }
}


<?php
namespace Payment\Utils;

/**
 * @author: helei
 * @createTime: 2016-06-07 21:01
 * @description:  常用的数组处理工具
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 */
class ArrayUtil
{
    /**
     * 移除空值的key
     * @param $para
     * @return array
     * @author helei
     */
    public static function paraFilter($para)
    {
        $paraFilter = [];
        foreach ($para as $key => $val) {
            if ($val === '' || $val === null) {
                continue;
            } else {
                if (!is_array($para[$key])) {
                    $para[$key] = is_bool($para[$key]) ? $para[$key] : trim($para[$key]);
                }

                $paraFilter[$key] = $para[$key];
            }
        }

        return $paraFilter;
    }

    /**
     * 删除一位数组中，指定的key与对应的值
     * @param array $inputs 要操作的数组
     * @param array|string $keys 需要删除的key的数组，或者用（,）链接的字符串
     * @return array
     */
    public static function removeKeys(array $inputs, $keys)
    {
        if (! is_array($keys)) {// 如果不是数组，需要进行转换
            $keys = explode(',', $keys);
        }

        if (empty($keys) || ! is_array($keys)) {
            return $inputs;
        }

        $flag = true;
        foreach ($keys as $key) {
            if (array_key_exists($key, $inputs)) {
                if (is_int($key)) {
                    $flag = false;
                }
                unset($inputs[$key]);
            }
        }

        if (! $flag) {
            $inputs = array_values($inputs);
        }
        return $inputs;
    }

    /**
     * 对输入的数组进行字典排序
     * @param array $param 需要排序的数组
     * @return array
     * @author helei
     */
    public static function arraySort(array $param)
    {
        ksort($param);
        reset($param);

        return $param;
    }

    /**
     * 把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
     * @param array $para 需要拼接的数组
     * @return string
     * @throws \Exception
     */
    public static function createLinkstring($para)
    {
        if (! is_array($para)) {
            throw new \Exception('必须传入数组参数');
        }

        reset($para);
        $arg = '';
        foreach ($para as $key => $val) {
            if (is_array($val)) {
                continue;
            }

            $arg .= $key . '=' . urldecode($val) . '&';
        }
        //去掉最后一个&字符
        $arg && $arg = substr($arg, 0, -1);

        //如果存在转义字符，那么去掉转义
        if (get_magic_quotes_gpc()) {
            $arg = stripslashes($arg);
        }

        return $arg;
    }

    /**
     * 获取一个数组中某个key的值，如果key为不存在，返回默认值
     * @param array $arr
     * @param $key
     * @param string $default
     *
     * @return string
     */
    public static function get(array $arr, $key, $default = '')
    {
        if (isset($arr[$key]) && ! empty($arr[$key])) {
            return $arr[$key];
        }

        return $default;
    }
}


<?php

namespace Payment\Common\Cmb;

use GuzzleHttp\Client;
use Payment\Common\BaseData;
use Payment\Common\BaseStrategy;
use Payment\Common\CmbConfig;
use Payment\Common\PayException;
use Payment\Config;

/**
 * Created by PhpStorm.
 * User: helei
 * Date: 2017/4/27
 * Time: 下午12:36
 */
abstract class CmbBaseStrategy implements BaseStrategy
{
    /**
     * 招商的配置文件
     * @var CmbConfig $config
     */
    protected $config;

    /**
     * 请求数据
     * @var BaseData $reqData
     */
    protected $reqData;

    /**
     * CmbBaseStrategy constructor.
     * @param array $config
     * @throws PayException
     */
    public function __construct(array $config)
    {
        try {
            $this->config = new CmbConfig($config);
        } catch (PayException $e) {
            throw $e;
        }
    }

    /**
     * 所有支付能力的入口
     * @param array $data
     * @return mixed
     * @throws PayException
     */
    public function handle(array $data)
    {
        $buildClass = $this->getBuildDataClass();

        try {
            $this->reqData = new $buildClass($this->config, $data);
        } catch (PayException $e) {
            throw $e;
        }

        $this->reqData->setSign();

        $data = $this->reqData->getData();

        return $this->retData($data);
    }

    /**
     * 处理微信的返回值并返回给客户端
     * @param array $ret
     * @return mixed
     * @author helei
     */
    protected function retData(array $ret)
    {
        $json = json_encode($ret, JSON_UNESCAPED_UNICODE);

        $reqData = [
            'url' => $this->config->getewayUrl,
            'name' => CmbConfig::REQ_FILED_NAME,
            'value' => $json,
        ];
        return $reqData;
    }

    /**
     * 发送完了请求
     * @param string $json
     * @return mixed
     * @throws PayException
     * @author helei
     */
    protected function sendReq($json)
    {
        $client = new Client([
            'timeout' => '10.0'
        ]);
        // @note: 微信部分接口并不需要证书支持。这里为了统一，全部携带证书进行请求
        $options = [
            'headers' => [
                'content-type' => 'multipart/form-data',
            ],
            'body' => $json,
            'http_errors' => false
        ];
        $response = $client->request('POST', $this->config->getewayUrl, $options);
        if ($response->getStatusCode() != '200') {
            throw new PayException('网络发生错误，请稍后再试curl返回码：' . $response->getReasonPhrase());
        }

        $body = $response->getBody()->getContents();
        $data = json_decode($body, true);
        // TODO 检查返回的数据是否被篡改
        $flag = $this->verifySign($data);
        if (!$flag) {
            throw new PayException('微信返回数据被篡改。请检查网络是否安全！');
        }

        $rspData = $data['rspData'];
        if ($rspData['rspCode'] !== CmbConfig::SUCC_TAG) {
            throw new PayException('招商返回错误提示：' . $rspData['rspMsg']);
        }

        return $rspData;
    }

    /**
     * 返回统一的交易状态  做一些转化，方便处理
     * @param $status
     * @return string
     * @author helei
     */
    protected function getTradeStatus($status)
    {
        switch ($status) {
            case '0':// 0:已结帐
                return Config::TRADE_STATUS_SUCC;
            case '1':// 1:已撤销
            case '2':// 2:部分结帐
            case '4':// 4:未结帐
            case '7':// 7:冻结交易-冻结金额已经全部结账
            case '8':// 8:冻结交易，冻结金额只结帐了一部分
            default:
                return Config::TRADE_STATUS_FAILD;// 以上状态全部设置为失败
        }
    }

    /**
     * 检查返回的数据是否正确
     * @param array $retData
     * @return bool
     */
    protected function verifySign(array $retData)
    {
        // todo
        return true;
    }
}


<?php
namespace Payment\Charge\Wx;

use Payment\Common\Weixin\Data\Charge\WapChargeData;
use Payment\Common\Weixin\WxBaseStrategy;

/**
 * @author: helei
 * @createTime: 2016-07-14 18:29
 * @description: 微信 h5 支付
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 */
class WxWapCharge extends WxBaseStrategy
{

    public function getBuildDataClass()
    {
        $this->config->tradeType = 'MWEB';
        return WapChargeData::class;
    }

    /**
     * 这里由于
     * @param array $ret
     * @return mixed
     */
    protected function retData(array $ret)
    {
        if ($this->config->returnRaw) {
            return $ret;
        }

        $wabUrl = $ret['mweb_url'];
        if ($this->config->returnUrl) {
            $wabUrl .= '&redirect_url=' . urlencode($this->config->returnUrl);
        }

        return $wabUrl;
    }
}


<?php
namespace Payment\Trans;

use Payment\Common\Weixin\Data\TransferData;
use Payment\Common\Weixin\WxBaseStrategy;
use Payment\Config;
use Payment\Utils\ArrayUtil;

/**
 * 微信企业付款接口
 * Class WxTransfer
 * @package Payment\Trans
 * anthor helei
 */
class WxTransfer extends WxBaseStrategy
{
    protected $reqUrl = 'https://api.mch.weixin.qq.com/{debug}/mmpaymkttransfers/promotion/transfers';

    public function getBuildDataClass()
    {
        return TransferData::class;
    }

    /**
     * 转款的返回数据
     * @param array $ret
     * @return mixed
     */
    protected function retData(array $ret)
    {
        if ($this->config->returnRaw) {
            $ret['channel'] = Config::WX_TRANSFER;
            return $ret;
        }

        // 请求失败，可能是网络
        if ($ret['return_code'] != 'SUCCESS') {
            return $retData = [
                'is_success'    => 'F',
                'error' => $ret['return_msg'],
                'channel'   => Config::WX_TRANSFER,
            ];
        }

        // 业务失败
        if ($ret['result_code'] != 'SUCCESS') {
            return $retData = [
                'is_success'    => 'F',
                'error' => $ret['err_code_des'],
                'channel'   => Config::WX_TRANSFER,
            ];
        }

        return $this->createBackData($ret);
    }

    /**
     * 返回数据
     * @param array $data
     * @return array
     */
    protected function createBackData(array $data)
    {
        $retData = [
            'is_success'    => 'T',
            'response'  => [
                'trans_no'   => $data['partner_trade_no'],
                'transaction_id'  => $data['payment_no'],
                'pay_date' => $data['payment_time'],// 企业付款成功时间  2015-05-19 15:26:59
                'device_info' => ArrayUtil::get($data, 'device_info', 'WEB'),
                'channel'   => Config::WX_TRANSFER,
            ],
        ];

        return $retData;
    }

    /**
     * 企业转账，不需要签名，使用返回true
     * @param array $retData
     * @return bool
     */
    protected function verifySign(array $retData)
    {
        return true;
    }
}


<?php
namespace Payment\Common\Ali\Data\Charge;

/**
 * @author: helei
 * @createTime: 2016-07-22 17:02
 * @description: 生成手机网站支付的数据
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 *
 * @property string $quit_url 手银台返回地址
 */
class WapChargeData extends ChargeBaseData
{

    /**
     * 业务请求参数的集合，最大长度不限，除公共参数外所有请求参数都必须放在这个参数中传递
     *
     * @return string
     */
    protected function getBizContent()
    {
        $content = [
            'body'          => strval($this->body),
            'subject'       => strval($this->subject),
            'out_trade_no'  => strval($this->order_no),
            'total_amount'  => strval($this->amount),

            // 销售产品码，商家和支付宝签约的产品码，为固定值QUICK_WAP_PAY
            'product_code'  => 'QUICK_WAP_PAY',
            'goods_type'    => $this->goods_type,
            'passback_params' => $this->return_param,
            // TODO 优惠信息待支持  业务扩展参数，待支持
            // 'promo_params' => '',
            // 'extend_params => '',
            'disable_pay_channels' => $this->limitPay,
            'store_id' => $this->store_id,
            // TODO 在收银台出现返回按钮
            'quit_url' => $this->quit_url,
        ];

        $timeExpire = $this->timeout_express;
        if (! empty($timeExpire)) {
            $express = floor(($timeExpire - strtotime($this->timestamp)) / 60);
            ($express > 0) && $content['timeout_express'] = $express . 'm';// 超时时间 统一使用分钟计算
        }

        return $content;
    }
}


<?php
namespace Payment\Notify;

use Payment\Common\ConfigInterface;

/**
 * @author: helei
 * @createTime: 2016-07-14 17:51
 * @description: 支付回调的策略接口
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 */
abstract class NotifyStrategy
{

    /**
     * 配置信息
     * @var ConfigInterface $config
     */
    protected $config;

    /**
     * 主要任务，验证返回的数据是否正确
     * @param PayNotifyInterface $notify
     * @return mixed
     * @author helei
     */
    final public function handle(PayNotifyInterface $notify)
    {
        // 获取异步通知的数据
        $notifyData = $this->getNotifyData();
        if ($notifyData === false) {// 失败，就返回错误
            return $this->replyNotify(false, '获取通知数据失败');
        }

        // 检查异步通知返回的数据是否有误
        $checkRet = $this->checkNotifyData($notifyData);
        if ($checkRet === false) {// 失败，就返回错误
            return $this->replyNotify(false, '返回数据验签失败，可能数据被篡改');
        }

        // 回调商户的业务逻辑
        $flag = $this->callback($notify, $notifyData);
        if ($flag) {
            $msg = 'OK';
        } else {
            $msg = '商户逻辑调用出错';
        }
        // 返回响应值
        return $this->replyNotify($flag, $msg);
    }

    /**
     * 回调商户的业务逻辑，根据返回的true  或者 false  向第三方返回数据
     * @param PayNotifyInterface $notify
     * @param array $notifyData
     *
     * @return boolean
     * @author helei
     */
    protected function callback(PayNotifyInterface $notify, array $notifyData)
    {
        $data = $this->getRetData($notifyData);

        if ($data === false) {
            return false;
        }

        return $notify->notifyProcess($data);
    }

    /**
     * 获取移除通知的数据  并进行简单处理（如：格式化为数组）
     *
     * 如果获取数据失败，返回false
     *
     * @return array|false
     * @author helei
     */
    abstract public function getNotifyData();

    /**
     * 检查异步通知的数据是否合法
     *
     * 如果检查失败，返回false
     *
     * @param array $data  由 $this->getNotifyData() 返回的数据
     * @return boolean
     * @author helei
     */
    abstract public function checkNotifyData(array $data);

    /**
     * 向客户端返回必要的数据
     * @param array $data 回调机构返回的回调通知数据
     * @return array|false
     * @author helei
     */
    abstract protected function getRetData(array $data);

    /**
     * 根据返回结果，回答支付机构。是否回调通知成功
     * @param boolean $flag 每次返回的bool值
     * @param string $msg 通知信息，错误原因
     * @return mixed
     * @author helei
     */
    abstract protected function replyNotify($flag, $msg = 'OK');
}


<?php
namespace Payment\Common\Cmb\Data\Charge;

use Payment\Common\Cmb\Data\CmbBaseData;
use Payment\Common\CmbConfig;
use Payment\Common\PayException;
use Payment\Config;

/**
 * Created by PhpStorm.
 * User: helei
 * Date: 2017/4/27
 * Time: 下午1:01
 *
 * @property string $date 订单日期,格式：yyyyMMdd
 * @property string $order_no  订单号, 10位数字，由商户生成，一天内不能重复。订单日期+订单号唯一定位一笔订单。
 * @property string $amount  金额, 格式：xxxx.xx  固定两位小数，最大11位整数
 * @property integer $timeout_express  过期时间
 * @property string $lon 经度，商户app获取的手机定位数据，如30.949505
 * @property string $lat 纬度，商户app获取的手机定位数据，如50.949506
 *
 */
class ChargeData extends CmbBaseData
{
    /**
     * 发送请求
     */
    protected function checkDataParam()
    {
        parent::checkDataParam();
        $amount = $this->amount;

        // 订单号交给支付系统自己检查

        // 检查金额不能低于0.01
        if (bccomp($amount, Config::PAY_MIN_FEE, 2) === -1) {
            throw new PayException('支付金额不能低于 ' . Config::PAY_MIN_FEE . ' 元');
        }

        // 设置ip地址
        $clientIp = $this->client_ip;
        if (empty($clientIp)) {
            $this->client_ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1';
        }

        $timeExpire = $this->timeout_express;
        if (! empty($timeExpire)) {
            $express = floor(($timeExpire - strtotime($this->dateTime)) / 60);

            if ($express > CmbConfig::MAX_EXPIRE_TIME || $express < 0) {// 招商规定
                $this->timeout_express = CmbConfig::MAX_EXPIRE_TIME;
            } else {
                $this->timeout_express = $express;
            }
        }
    }

    /**
     * 请求数据
     */
    protected function getReqData()
    {
        $reqData = [
            'dateTime' => $this->dateTime,
            'branchNo' => $this->branchNo,
            'merchantNo' => $this->merchantNo,
            'date' => $this->date ? $this->date : date('Ymd', time()),
            'orderNo' => $this->order_no,
            'amount' => $this->amount,
            'expireTimeSpan' => $this->timeout_express ? $this->timeout_express  : '',
            'payNoticeUrl' => $this->notifyUrl,
            'payNoticePara' => $this->return_param ? $this->return_param : '',
            'returnUrl' => $this->returnUrl ? $this->returnUrl : '',
            'clientIP' => $this->client_ip,
            'cardType' => $this->limitPay ? $this->limitPay : '',
            'agrNo' => $this->agr_no,
            'merchantSerialNo' => $this->serial_no ? $this->serial_no : '',
            'userID' => $this->user_id ? $this->user_id : '',
            'mobile' => $this->mobile ? $this->mobile : '',
            'lon' => $this->lon ? $this->lon : '',
            'lat' => $this->lat ? $this->lat : '',
            'riskLevel' => $this->risk_level ? $this->risk_level : '',
            'signNoticeUrl' => $this->signNoticeUrl ? $this->signNoticeUrl : '',
            'signNoticePara' => $this->return_param ? $this->return_param : '',

            // 暂时先不支持下面方式
            'extendInfo' => '',
            'extendInfoEncrypType' => '',
        ];

        // 这里不能进行过滤空值，招商的空值也要加入签名中
        return $reqData;
    }
}


<?php
namespace Payment\Client;

use Payment\Common\PayException;
use Payment\Config;
use Payment\HelperContext;

/**
 * @author: helei
 * @createTime: 2017-09-02 18:20
 * @description: 招商银行的辅助类
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 *
 * Class Helper
 * @package Payment\Client
 */
class Helper
{
    private static $supportChannel = [
        Config::CMB_BIND,// 招商绑卡操作
        Config::CMB_PUB_KEY,// 招商公钥查询操作
    ];

    /**
     * 辅助类实例
     * @var HelperContext
     */
    protected static $instance;

    protected static function getInstance($channel, $config)
    {
        /* 设置内部字符编码为 UTF-8 */
        mb_internal_encoding("UTF-8");

        if (is_null(self::$instance)) {
            static::$instance = new HelperContext();
        }

        try {
            static::$instance->initHelper($channel, $config);
        } catch (PayException $e) {
            throw $e;
        }

        return static::$instance;
    }

    /**
     * @param string $channel
     * @param array $config
     * @param array $metadata
     *
     * @return mixed
     * @throws PayException
     */
    public static function run($channel, $config, array $metadata = [])
    {
        if (! in_array($channel, self::$supportChannel)) {
            throw new PayException('sdk当前不支持该渠道，当前仅支持：' . implode(',', self::$supportChannel));
        }

        try {
            $instance = self::getInstance($channel, $config);

            $ret = $instance->helper($metadata);
        } catch (PayException $e) {
            throw $e;
        }

        return $ret;
    }
}


<?php
namespace Payment\Notify;

use Payment\Common\CmbConfig;
use Payment\Common\PayException;
use Payment\Config;
use Payment\Utils\ArrayUtil;
use Payment\Utils\RsaEncrypt;

/**
 * 招商回调处理
 * Class CmbNotify
 * @package Payment\Notify
 * @link      https://www.gitbook.com/book/helei112g1/payment-sdk/details
 * @link      https://helei112g.github.io/
 *
 */
class CmbNotify extends NotifyStrategy
{

    /**
     * CmbNotify constructor.
     * @param array $config
     * @throws PayException
     */
    public function __construct(array $config)
    {
        try {
            $this->config = new CmbConfig($config);
        } catch (PayException $e) {
            throw $e;
        }
    }

    public function getNotifyData()
    {
        $data = empty($_POST) ? $_GET : $_POST;
        if (empty($data) || ! is_array($data)) {
            return false;
        }

        $retData = json_decode($data[CmbConfig::REQ_FILED_NAME], true);
        return $retData;
    }

    /**
     * 对返回信息验证签名
     * @param array $data
     * @return boolean
     */
    public function checkNotifyData(array $data)
    {
        $signType = strtoupper($data['signType']);
        $sign = $data['sign'];

        // 1. 对待签名参数数组排序
        $values = ArrayUtil::arraySort($data['noticeData']);
        // 2. 将排序后的参数与其对应值，组合成“参数=参数值”的格式,用&字符连接起来
        $preStr = ArrayUtil::createLinkstring($values);

        if ($signType === 'RSA') {// 使用rsa方式
            $rsa = new RsaEncrypt($this->config->rsaPubKey);

            return $rsa->rsaVerify($preStr, $sign);
        } else {
            return false;
        }
    }

    /**
     * 向客户端返回相关数据
     * @param array $data
     * @return array
     */
    protected function getRetData(array $data)
    {
        $noticeData = $data['noticeData'];
        $noticeType = $noticeData['noticeType'];
        if ($noticeType === CmbConfig::NOTICE_PAY) {
            $channel = Config::CMB_CHARGE;
        } elseif ($noticeType === CmbConfig::NOTICE_SIGN) {
            $channel = Config::CMB_BIND;
        } else {
            $channel = 'other';
        }

        if ($this->config->returnRaw) {
            $data['channel'] = $channel;
            return $data;
        } elseif ($noticeType === CmbConfig::NOTICE_PAY) {
            $retData = [
                'amount'   => $noticeData['amount'],
                'channel'   => $channel,
                'date' => $noticeData['date'],
                'order_no'   => $noticeData['orderNo'],
                'trade_state'   => Config::TRADE_STATUS_SUCC,// 招商的订单只会成功
                'transaction_id'   => $noticeData['bankSerialNo'],
                'time_end'   => date('Y-m-d H:i:s', strtotime($noticeData['dateTime'])),// Y-m-d H:i:s
                'discount_fee' => $noticeData['discountAmount'],// 优惠金额,格式：xxxx.xx  无优惠时返回0.00
                'card_type' => $noticeData['cardType'],// 卡类型,02：一卡通；03：信用卡；07：他行卡
                'return_param' => $noticeData['merchantPara'],
                'discount_flag' => $noticeData['discountFlag'],
                'notice_no' => $noticeData['noticeSerialNo'],
            ];
        } elseif ($noticeType === CmbConfig::NOTICE_SIGN) {
            $retData = [
                'user_id' => $noticeData['userID'],
                'no_pwd_pay' => $noticeData['noPwdPay'],
                'notice_no' => $noticeData['noticeSerialNo'],
                'agr_no' => $noticeData['agrNo'],
                'rsp_msg' => $noticeData['rspMsg'],
                'return_param' => $noticeData['noticePara'],
                'user_pid_hash' => $noticeData['userPidHash'],
                'user_pid_type' => $noticeData['userPidType'],
                'channel'   => $channel,
            ];
        } else {
            $retData = $noticeData;
        }

        return $retData;
    }

    /**
     * 招商只需要返回状态码
     * @param bool $flag
     * @param string $msg
     * @return string
     */
    protected function replyNotify($flag, $msg = 'OK')
    {
        if ($flag) {
            header('HTTP/1.1 200 OK');
            return $msg;
        } else {
            header('HTTP/1.1 503 Service Unavailable');
            return $msg;
        }
    }
}