123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505 |
- <?php
- namespace Wechat;
- use Wechat\Lib\Tools;
- /**
- * 微信支付SDK
- * @author zoujingli <zoujingli@qq.com>
- * @date 2015/05/13 12:12:00
- */
- class WechatPay {
- /** 支付接口基础地址 */
- const MCH_BASE_URL = 'https://api.mch.weixin.qq.com';
- /** 公众号appid */
- public $appid;
- /** 商户身份ID */
- public $mch_id;
- /** 商户支付密钥Key */
- public $partnerKey;
- /** 证书路径 */
- public $ssl_cer;
- public $ssl_key;
- /** 执行错误消息及代码 */
- public $errMsg;
- public $errCode;
- /**
- * WechatPay constructor.
- * @param array $options
- */
- public function __construct($options = array()) {
- $config = Loader::config($options);
- $this->appid = isset($config['appid']) ? $config['appid'] : '';
- $this->mch_id = isset($config['mch_id']) ? $config['mch_id'] : '';
- $this->partnerKey = isset($config['partnerkey']) ? $config['partnerkey'] : '';
- $this->ssl_cer = isset($config['ssl_cer']) ? $config['ssl_cer'] : '';
- $this->ssl_key = isset($config['ssl_key']) ? $config['ssl_key'] : '';
- }
- /**
- * 设置标配的请求参数,生成签名,生成接口参数xml
- * @param array $data
- * @return string
- */
- protected function createXml($data) {
- if (!isset($data['wxappid']) && !isset($data['mch_appid']) && !isset($data['appid'])) {
- $data['appid'] = $this->appid;
- }
- if (!isset($data['mchid']) && !isset($data['mch_id'])) {
- $data['mch_id'] = $this->mch_id;
- }
- isset($data['nonce_str']) || $data['nonce_str'] = Tools::createNoncestr();
- $data["sign"] = Tools::getPaySign($data, $this->partnerKey);
- return Tools::arr2xml($data);
- }
- /**
- * POST提交XML
- * @param array $data
- * @param string $url
- * @return mixed
- */
- public function postXml($data, $url) {
- return Tools::httpPost($url, $this->createXml($data));
- }
- /**
- * 使用证书post请求XML
- * @param array $data
- * @param string $url
- * @return mixed
- */
- function postXmlSSL($data, $url) {
- return Tools::httpsPost($url, $this->createXml($data), $this->ssl_cer, $this->ssl_key);
- }
- /**
- * POST提交获取Array结果
- * @param array $data 需要提交的数据
- * @param string $url
- * @param string $method
- * @return array
- */
- public function getArrayResult($data, $url, $method = 'postXml') {
- return Tools::xml2arr($this->$method($data, $url));
- }
- /**
- * 解析返回的结果
- * @param array $result
- * @return bool|array
- */
- protected function _parseResult($result) {
- if (empty($result)) {
- $this->errCode = 'result error';
- $this->errMsg = '解析返回结果失败';
- return false;
- }
- if ($result['return_code'] !== 'SUCCESS') {
- $this->errCode = $result['return_code'];
- $this->errMsg = $result['return_msg'];
- return false;
- }
- if (isset($result['err_code']) && $result['err_code'] !== 'SUCCESS') {
- $this->errMsg = $result['err_code_des'];
- $this->errCode = $result['err_code'];
- return false;
- }
- return $result;
- }
- /**
- * 支付通知验证处理
- * @return bool|array
- */
- public function getNotify() {
- $notifyInfo = (array)simplexml_load_string(file_get_contents("php://input"), 'SimpleXMLElement', LIBXML_NOCDATA);
- if (empty($notifyInfo)) {
- Tools::log('Payment notification forbidden access.', 'ERR');
- $this->errCode = '404';
- $this->errMsg = 'Payment notification forbidden access.';
- return false;
- }
- if (empty($notifyInfo['sign'])) {
- Tools::log('Payment notification signature is missing.' . var_export($notifyInfo, true), 'ERR');
- $this->errCode = '403';
- $this->errMsg = 'Payment notification signature is missing.';
- return false;
- }
- $data = $notifyInfo;
- unset($data['sign']);
- if ($notifyInfo['sign'] !== Tools::getPaySign($data, $this->partnerKey)) {
- Tools::log('Payment notification signature verification failed.' . var_export($notifyInfo, true), 'ERR');
- $this->errCode = '403';
- $this->errMsg = 'Payment signature verification failed.';
- return false;
- }
- Tools::log('Payment notification signature verification success.' . var_export($notifyInfo, true), 'MSG');
- $this->errCode = '0';
- $this->errMsg = '';
- return $notifyInfo;
- }
- /**
- * 支付XML统一回复
- * @param array $data 需要回复的XML内容数组
- * @param bool $isReturn 是否返回XML内容,默认不返回
- * @return string
- */
- public function replyXml(array $data, $isReturn = false) {
- $xml = Tools::arr2xml($data);
- if ($isReturn) {
- return $xml;
- }
- ob_clean();
- exit($xml);
- }
- /**
- * 获取预支付ID
- * @param string $openid 用户openid,JSAPI必填
- * @param string $body 商品标题
- * @param string $out_trade_no 第三方订单号
- * @param int $total_fee 订单总价
- * @param string $notify_url 支付成功回调地址
- * @param string $trade_type 支付类型JSAPI|NATIVE|APP
- * @param string $goods_tag 商品标记,代金券或立减优惠功能的参数
- * @return bool|string
- */
- public function getPrepayId($openid, $body, $out_trade_no, $total_fee, $notify_url, $trade_type = "JSAPI", $goods_tag = null) {
- $postdata = array(
- "body" => $body,
- "out_trade_no" => $out_trade_no,
- "total_fee" => $total_fee,
- "notify_url" => $notify_url,
- "trade_type" => $trade_type,
- "spbill_create_ip" => Tools::getAddress()
- );
- empty($goods_tag) || $postdata['goods_tag'] = $goods_tag;
- empty($openid) || $postdata['openid'] = $openid;
- $result = $this->getArrayResult($postdata, self::MCH_BASE_URL . '/pay/unifiedorder');
- if (false === $this->_parseResult($result)) {
- return false;
- }
- return in_array($trade_type, array('JSAPI', 'APP')) ? $result['prepay_id'] : $result['code_url'];
- }
- /**
- * 获取二维码预支付ID
- * @param string $openid 用户openid,JSAPI必填
- * @param string $body 商品标题
- * @param string $out_trade_no 第三方订单号
- * @param int $total_fee 订单总价
- * @param string $notify_url 支付成功回调地址
- * @param string $goods_tag 商品标记,代金券或立减优惠功能的参数
- * @return bool|string
- */
- public function getQrcPrepayId($openid, $body, $out_trade_no, $total_fee, $notify_url, $goods_tag = null) {
- $postdata = array(
- "body" => $body,
- "out_trade_no" => $out_trade_no,
- "total_fee" => $total_fee,
- "notify_url" => $notify_url,
- "trade_type" => 'NATIVE',
- "spbill_create_ip" => Tools::getAddress()
- );
- empty($goods_tag) || $postdata['goods_tag'] = $goods_tag;
- empty($openid) || $postdata['openid'] = $openid;
- $result = $this->getArrayResult($postdata, self::MCH_BASE_URL . '/pay/unifiedorder');
- if (false === $this->_parseResult($result) || empty($result['prepay_id'])) {
- return false;
- }
- return $result['prepay_id'];
- }
- /**
- * 获取支付规二维码
- * @param string $product_id 商户定义的商品id 或者订单号
- * @return string
- */
- public function getQrcPayUrl($product_id) {
- $data = array(
- 'appid' => $this->appid,
- 'mch_id' => $this->mch_id,
- 'time_stamp' => (string)time(),
- 'nonce_str' => Tools::createNoncestr(),
- 'product_id' => (string)$product_id,
- );
- $data['sign'] = Tools::getPaySign($data, $this->partnerKey);
- return "weixin://wxpay/bizpayurl?" . http_build_query($data);
- }
- /**
- * 创建JSAPI支付参数包
- * @param string $prepay_id
- * @return array
- */
- public function createMchPay($prepay_id) {
- $option = array();
- $option["appId"] = $this->appid;
- $option["timeStamp"] = (string)time();
- $option["nonceStr"] = Tools::createNoncestr();
- $option["package"] = "prepay_id={$prepay_id}";
- $option["signType"] = "MD5";
- $option["paySign"] = Tools::getPaySign($option, $this->partnerKey);
- $option['timestamp'] = $option['timeStamp'];
- return $option;
- }
- /**
- * 关闭订单
- * @param string $out_trade_no
- * @return bool
- */
- public function closeOrder($out_trade_no) {
- $data = array('out_trade_no' => $out_trade_no);
- $result = $this->getArrayResult($data, self::MCH_BASE_URL . '/pay/closeorder');
- if (false === $this->_parseResult($result)) {
- return false;
- }
- return ($result['return_code'] === 'SUCCESS');
- }
- /**
- * 查询订单详情
- * @param $out_trade_no
- * @return bool|array
- */
- public function queryOrder($out_trade_no) {
- $data = array('out_trade_no' => $out_trade_no);
- $result = $this->getArrayResult($data, self::MCH_BASE_URL . '/pay/orderquery');
- if (false === $this->_parseResult($result)) {
- return false;
- }
- return $result;
- }
- /**
- * 订单退款接口
- * @param string $out_trade_no 商户订单号
- * @param string $transaction_id 微信订单号
- * @param string $out_refund_no 商户退款订单号
- * @param int $total_fee 商户订单总金额
- * @param int $refund_fee 退款金额
- * @param int|null $op_user_id 操作员ID,默认商户ID
- * @param string $refund_account 退款资金来源
- * 仅针对老资金流商户使用
- * REFUND_SOURCE_UNSETTLED_FUNDS --- 未结算资金退款(默认使用未结算资金退款)
- * REFUND_SOURCE_RECHARGE_FUNDS --- 可用余额退款
- * @return bool
- */
- public function refund($out_trade_no, $transaction_id, $out_refund_no, $total_fee, $refund_fee, $op_user_id = null, $refund_account = '') {
- $data = array();
- $data['out_trade_no'] = $out_trade_no;
- $data['transaction_id'] = $transaction_id;
- $data['out_refund_no'] = $out_refund_no;
- $data['total_fee'] = $total_fee;
- $data['refund_fee'] = $refund_fee;
- $data['op_user_id'] = empty($op_user_id) ? $this->mch_id : $op_user_id;
- !empty($refund_account) && $data['refund_account'] = $refund_account;
- $result = $this->getArrayResult($data, self::MCH_BASE_URL . '/secapi/pay/refund', 'postXmlSSL');
- if (false === $this->_parseResult($result)) {
- return false;
- }
- return ($result['return_code'] === 'SUCCESS');
- }
- /**
- * 退款查询接口
- * @param string $out_trade_no
- * @return bool|array
- */
- public function refundQuery($out_trade_no) {
- $data = array();
- $data['out_trade_no'] = $out_trade_no;
- $result = $this->getArrayResult($data, self::MCH_BASE_URL . '/pay/refundquery');
- if (false === $this->_parseResult($result)) {
- return false;
- }
- return $result;
- }
- /**
- * 获取对账单
- * @param string $bill_date 账单日期,如 20141110
- * @param string $bill_type ALL|SUCCESS|REFUND|REVOKED
- * @return bool|array
- */
- public function getBill($bill_date, $bill_type = 'ALL') {
- $data = array();
- $data['bill_date'] = $bill_date;
- $data['bill_type'] = $bill_type;
- $result = $this->postXml($data, self::MCH_BASE_URL . '/pay/downloadbill');
- $json = Tools::xml2arr($result);
- if (!empty($json) && false === $this->_parseResult($json)) {
- return false;
- }
- return $json;
- }
- /**
- * 发送现金红包
- * @param string $openid 红包接收者OPENID
- * @param int $total_amount 红包总金额
- * @param string $mch_billno 商户订单号
- * @param string $sendname 商户名称
- * @param string $wishing 红包祝福语
- * @param string $act_name 活动名称
- * @param string $remark 备注信息
- * @param null|int $total_num 红包发放总人数(大于1为裂变红包)
- * @param null|string $scene_id 场景id
- * @param string $risk_info 活动信息
- * @param null|string $consume_mch_id 资金授权商户号
- * @return array|bool
- * @link https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_5
- */
- public function sendRedPack($openid, $total_amount, $mch_billno, $sendname, $wishing, $act_name, $remark, $total_num = 1, $scene_id = null, $risk_info = '', $consume_mch_id = null) {
- $data = array();
- $data['mch_billno'] = $mch_billno; // 商户订单号 mch_id+yyyymmdd+10位一天内不能重复的数字
- $data['wxappid'] = $this->appid;
- $data['send_name'] = $sendname; //商户名称
- $data['re_openid'] = $openid; //红包接收者
- $data['total_amount'] = $total_amount; //红包总金额
- $data['total_num'] = '1'; //发放人数据
- $data['wishing'] = $wishing; //红包祝福语
- $data['client_ip'] = Tools::getAddress(); //调用接口的机器Ip地址
- $data['act_name'] = $act_name; //活动名称
- $data['remark'] = $remark; //备注信息
- $data['total_num'] = $total_num;
- !empty($scene_id) && $data['scene_id'] = $scene_id;
- !empty($risk_info) && $data['risk_info'] = $risk_info;
- !empty($consume_mch_id) && $data['consume_mch_id'] = $consume_mch_id;
- if ($total_num > 1) {
- $data['amt_type'] = 'ALL_RAND';
- $api = self::MCH_BASE_URL . '/mmpaymkttransfers/sendgroupredpack';
- } else {
- $api = self::MCH_BASE_URL . '/mmpaymkttransfers/sendredpack';
- }
- $result = $this->postXmlSSL($data, $api);
- $json = Tools::xml2arr($result);
- if (!empty($json) && false === $this->_parseResult($json)) {
- return false;
- }
- return $json;
- }
- /**
- * 现金红包状态查询
- * @param string $billno
- * @return bool|array
- * @link https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_7&index=6
- */
- public function queryRedPack($billno) {
- $data['mch_billno'] = $billno;
- $data['bill_type'] = 'MCHT';
- $result = $this->postXmlSSL($data, self::MCH_BASE_URL . '/mmpaymkttransfers/gethbinfo');
- $json = Tools::xml2arr($result);
- if (!empty($json) && false === $this->_parseResult($json)) {
- return false;
- }
- return $json;
- }
- /**
- * 企业付款
- * @param string $openid 红包接收者OPENID
- * @param int $amount 红包总金额
- * @param string $billno 商户订单号
- * @param string $desc 备注信息
- * @return bool|array
- * @link https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2
- */
- public function transfers($openid, $amount, $billno, $desc) {
- $data = array();
- $data['mchid'] = $this->mch_id;
- $data['mch_appid'] = $this->appid;
- $data['partner_trade_no'] = $billno;
- $data['openid'] = $openid;
- $data['amount'] = $amount;
- $data['check_name'] = 'NO_CHECK'; #不验证姓名
- $data['spbill_create_ip'] = Tools::getAddress(); //调用接口的机器Ip地址
- $data['desc'] = $desc; //备注信息
- $result = $this->postXmlSSL($data, self::MCH_BASE_URL . '/mmpaymkttransfers/promotion/transfers');
- $json = Tools::xml2arr($result);
- if (!empty($json) && false === $this->_parseResult($json)) {
- return false;
- }
- return $json;
- }
- /**
- * 企业付款查询
- * @param string $billno
- * @return bool|array
- * @link https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_3
- */
- public function queryTransfers($billno) {
- $data['appid'] = $this->appid;
- $data['mch_id'] = $this->mch_id;
- $data['partner_trade_no'] = $billno;
- $result = $this->postXmlSSL($data, self::MCH_BASE_URL . '/mmpaymkttransfers/gettransferinfo');
- $json = Tools::xml2arr($result);
- if (!empty($json) && false === $this->_parseResult($json)) {
- return false;
- }
- return $json;
- }
- /**
- * 二维码链接转成短链接
- * @param string $url 需要处理的长链接
- * @return bool|string
- */
- public function shortUrl($url) {
- $data = array();
- $data['long_url'] = $url;
- $result = $this->getArrayResult($data, self::MCH_BASE_URL . '/tools/shorturl');
- if (!$result || $result['return_code'] !== 'SUCCESS') {
- $this->errCode = $result['return_code'];
- $this->errMsg = $result['return_msg'];
- return false;
- }
- if (isset($result['err_code']) && $result['err_code'] !== 'SUCCESS') {
- $this->errMsg = $result['err_code_des'];
- $this->errCode = $result['err_code'];
- return false;
- }
- return $result['short_url'];
- }
- /**
- * 发放代金券
- * @param int $coupon_stock_id 代金券批次id
- * @param string $partner_trade_no 商户此次发放凭据号(格式:商户id+日期+流水号),商户侧需保持唯一性
- * @param string $openid Openid信息
- * @param string $op_user_id 操作员帐号, 默认为商户号 可在商户平台配置操作员对应的api权限
- * @return bool|array
- * @link https://pay.weixin.qq.com/wiki/doc/api/tools/sp_coupon.php?chapter=12_3
- */
- public function sendCoupon($coupon_stock_id, $partner_trade_no, $openid, $op_user_id = null) {
- $data = array();
- $data['appid'] = $this->appid;
- $data['coupon_stock_id'] = $coupon_stock_id;
- $data['openid_count'] = 1;
- $data['partner_trade_no'] = $partner_trade_no;
- $data['openid'] = $openid;
- $data['op_user_id'] = empty($op_user_id) ? $this->mch_id : $op_user_id;
- $result = $this->postXmlSSL($data, self::MCH_BASE_URL . '/mmpaymkttransfers/send_coupon');
- $json = Tools::xml2arr($result);
- if (!empty($json) && false === $this->_parseResult($json)) {
- return false;
- }
- return $json;
- }
- }
|