- - 缴存保证金
- - 退回保证金
+ - 正常开启的商户
+ - 已关闭商户
@@ -198,8 +198,10 @@
@@ -207,9 +209,8 @@
@@ -225,7 +226,7 @@
layui.payTable = table.render({
elem: '#pay_list',
- title: '保证金列表',
+ title: '正常开启的商户',
toolbar: '#toolbarDemo',
url: '/admin/margin/lst',
page: true,
@@ -295,7 +296,7 @@
field: 'right',
title: '操作',
toolbar: '#barDemo',
- width: 190,
+ width: 200,
align: 'center'
}
]
diff --git a/app/common/jobs/interfaces/JobInterface.php b/app/common/jobs/interfaces/JobInterface.php
new file mode 100644
index 0000000..644bc00
--- /dev/null
+++ b/app/common/jobs/interfaces/JobInterface.php
@@ -0,0 +1,17 @@
+退返保证金审核通过的消息
+ *
+ * @author:刘孝全
+ * @email:q8197264@126.com
+ * @date :2023年03月17日
+ */
+namespace app\common\jobs\merchant;
+
+
+use app\common\model\merchant\store\product\Product;
+use app\common\model\merchant\system\merchant\Merchant;
+use app\common\jobs\interfaces\JobInterface;
+use think\facade\Log;
+use think\queue\Job;
+
+class ChangeMerchantStatusJob implements JobInterface
+{
+
+ public function fire($job, $merId)
+ {
+ $rows=0;
+ $merchant = app()->make(Merchant::class)->get($merId);
+ if ($merchant) {
+ $where = [
+ 'mer_status' => ($merchant['is_del'] || !$merchant['mer_state'] || !$merchant['status']) ? 0 : 1
+ ];
+ $rows = app()->make(Product::class)->changeMerchantProduct($merId, $where);
+ }
+ $job->delete();
+ return $rows;
+ }
+
+ public function failed($data)
+ {
+ // TODO: Implement failed() method.
+ }
+}
diff --git a/app/common/model/merchant/store/product/Product.php b/app/common/model/merchant/store/product/Product.php
new file mode 100644
index 0000000..81bc30e
--- /dev/null
+++ b/app/common/model/merchant/store/product/Product.php
@@ -0,0 +1,550 @@
+
+// +----------------------------------------------------------------------
+
+namespace app\common\model\merchant\store\product;
+
+use app\common\dao\store\StoreSeckillActiveDao;
+use app\common\model\BaseModel;
+use app\common\model\store\coupon\StoreCouponProduct;
+use app\common\model\store\Guarantee;
+use app\common\model\store\GuaranteeTemplate;
+use app\common\model\store\GuaranteeValue;
+use app\common\model\store\parameter\ParameterValue;
+use app\common\model\store\shipping\ShippingTemplate;
+use app\common\model\store\StoreBrand;
+use app\common\model\store\StoreCategory;
+use app\common\model\store\StoreSeckillActive;
+use app\common\model\system\merchant\Merchant;
+use app\common\repositories\store\StoreCategoryRepository;
+use crmeb\services\VicWordService;
+use Darabonba\GatewaySpi\Models\InterceptorContext\request;
+use think\db\BaseQuery;
+use think\facade\Db;
+use think\model\concern\SoftDelete;
+
+/**
+ * TODO:
+ */
+class Product extends BaseModel
+{
+ use SoftDelete;
+
+
+ protected $deleteTime = 'is_del';
+ protected $defaultSoftDelete = 0;
+
+
+ /**
+ * @Author:Qinii
+ * @Date: 2020/5/8
+ * @return string
+ */
+ public static function tablePk(): string
+ {
+ return 'product_id';
+ }
+
+ /**
+ * @Author:Qinii
+ * @Date: 2020/5/8
+ * @return string
+ */
+ public static function tableName(): string
+ {
+ return 'store_product';
+ }
+
+ /*
+ * -----------------------------------------------------------------------------------------------------------------
+ * 属性
+ * -----------------------------------------------------------------------------------------------------------------
+ */
+ public function getSliderImageAttr($value)
+ {
+ return $value ? explode(',',$value) : [];
+ }
+ public function getGiveCouponIdsAttr($value)
+ {
+ return $value ? explode(',',$value) : [];
+ }
+ public function getMaxExtensionAttr($value)
+ {
+ if($this->extension_type){
+ $org_extension = ($this->attrValue()->order('extension_two DESC')->value('extension_one'));
+ } else {
+ $org_extension = bcmul(($this->attrValue()->order('price DESC')->value('price')) , systemConfig('extension_one_rate'),2);
+ }
+ $spreadUser = (request()->isLogin() && request()->userType() == 1 ) ? request()->userInfo() : null;
+ if ($spreadUser && $spreadUser->brokerage_level > 0 && $spreadUser->brokerage && $spreadUser->brokerage->extension_one_rate > 0) {
+ $org_extension = bcmul($org_extension, 1 + $spreadUser->brokerage->extension_one_rate, 2);
+ }
+ return $org_extension;
+ }
+ public function getMinExtensionAttr($value)
+ {
+ if($this->extension_type){
+ $org_extension = ($this->attrValue()->order('extension_two ASC')->value('extension_two'));
+ } else {
+ $org_extension = bcmul(($this->attrValue()->order('price ASC')->value('price')) , systemConfig('extension_one_rate'),2);
+ }
+ $spreadUser = (request()->isLogin() && request()->userType() == 1 ) ? request()->userInfo() : null;
+ if ($spreadUser && $spreadUser->brokerage_level > 0 && $spreadUser->brokerage && $spreadUser->brokerage->extension_one_rate > 0) {
+ $org_extension = bcmul($org_extension, 1 + $spreadUser->brokerage->extension_one_rate, 2);
+ }
+ return $org_extension;
+ }
+
+ public function check()
+ {
+ if(!$this || !$this->is_show || !$this->is_used || !$this->status || $this->is_del || !$this->mer_status) return false;
+ return true;
+ }
+
+ /**
+ * TODO 秒杀商品结束时间
+ * @return false|int
+ * @author Qinii
+ * @day 2020-08-15
+ */
+ public function getEndTimeAttr()
+ {
+ if($this->product_type !== 1) return true;
+ $day = date('Y-m-d',time());
+ $_day = strtotime($day);
+ $end_day = strtotime($this->seckillActive['end_day']);
+ if($end_day >= $_day)
+ return strtotime($day.$this->seckillActive['end_time'].':00:00');
+ if($end_day < strtotime($day))
+ return strtotime(date('Y-m-d',$end_day).$this->seckillActive['end_time'].':00:00');
+ }
+
+ /**
+ * TODO 秒杀商品状态
+ * @return array|int
+ * @author Qinii
+ * @day 2020-08-19
+ */
+ public function getSeckillStatusAttr()
+ {
+ if($this->product_type !== 1) return true;
+ $day = strtotime(date('Y-m-d',time()));
+ $_h = date('H',time());
+ $start_day = strtotime($this->seckillActive['start_day']);
+ $end_day = strtotime($this->seckillActive['end_day']);
+ if(!$this->seckillActive) return '';
+ if($this->seckillActive['status'] !== -1){
+ //还未开始
+ if($start_day > time() || $this->is_show !== 1)return 0;
+ //已结束
+ if($end_day < $day) return -1;
+ //开始 - 结束
+ if($start_day <= $day && $day <= $end_day){
+ //未开始
+ if($this->seckillActive['start_time'] > $_h) return 0;
+ //已结束
+ if($this->seckillActive['end_time'] <= $_h) return -1;
+ //进行中
+ if($this->seckillActive['start_time'] <= $_h && $this->seckillActive['end_time'] > $_h) return 1;
+ }
+ }
+ //已结束
+ return -1;
+
+ }
+
+ public function getImageAttr($value)
+ {
+ if (is_int(strpos($value, 'http'))){
+ return $value;
+ }else{
+ return rtrim(systemConfig('site_url'),'/') .$value;
+ }
+ }
+
+ public function getTopReplyAttr()
+ {
+ $res = ProductReply::where('product_id',$this->product_id)->where('is_del',0)->with(['orderProduct'])->field('reply_id,uid,nickname,merchant_reply_content,avatar,order_product_id,product_id,product_score,service_score,postage_score,comment,pics,rate,create_time')
+ ->order('sort DESC,create_time DESC')->limit(1)->find();
+ if(!$res) return null;
+ if ($res['orderProduct'])
+ $res['sku'] = $res['orderProduct']['cart_info']['productAttr']['sku'];
+ unset($res['orderProduct']);
+ if (strlen($res['nickname']) > 1) {
+ $str = mb_substr($res['nickname'],0,1) . '*';
+ if (strlen($res['nickname']) > 2) {
+ $str .= mb_substr($res['nickname'], -1,1);
+ }
+ $res['nickname'] = $str;
+ }
+
+ return $res;
+ }
+
+ public function getUsStatusAttr()
+ {
+ return ($this->status == 1) ? ($this->is_used == 1 ? ( $this->is_show ? 1 : 0 ) : -1) : -1;
+ }
+
+ public function getGuaranteeTemplateAttr()
+ {
+ $gua = GuaranteeTemplate::where('guarantee_template_id',$this->guarantee_template_id)->where('status',1)->where('is_del',0)->find();
+ if(!$gua) return [];
+ $guarantee_id = GuaranteeValue::where('guarantee_template_id',$this->guarantee_template_id)->column('guarantee_id');
+ return Guarantee::where('guarantee_id','in',$guarantee_id)->where('status',1)->where('is_del',0)->select();
+ }
+
+ public function getMaxIntegralAttr()
+ {
+ if(systemConfig('integral_status') && merchantConfig($this->mer_id,'mer_integral_status')){
+ $price = ($this->attrValue()->order('price DESC')->value('price'));
+ $rate = ($this->integral_rate < 0) ? merchantConfig($this->mer_id,'mer_integral_rate') : $this->integral_rate;
+ $rate = $rate < 0 ? $rate / 100 : 0;
+ return bcmul($price ,$rate,2);
+ }
+ return '0';
+ }
+
+ public function getHotRankingAttr()
+ {
+ if ($this->product_type == 0) {
+ $where = [
+ 'is_show' => 1,
+ 'status' => 1,
+ 'is_used' => 1,
+ 'product_type' => 0,
+ 'mer_status' => 1,
+ 'is_gift_bag' => 0,
+ 'cate_id' => $this->cate_id
+ ];
+ self::where($where)->order('sales DESC');
+ }
+ }
+
+ /**
+ * TODO 商品参数
+ * @author Qinii
+ * @day 2022/11/24
+ */
+ public function getParamsAttr()
+ {
+ if(in_array($this->product_type,[0,2])) {
+ $product_id = $this->product_id;
+ } else {
+ $product_id = $this->old_product_id;
+ }
+ return ParameterValue::where('product_id',$product_id)->order('parameter_value_id ASC')->select();
+ }
+
+ public function getParamTempIdAttr($value)
+ {
+ return $value ? explode(',',$value) : $value;
+ }
+
+ /*
+ * -----------------------------------------------------------------------------------------------------------------
+ * 关联模型
+ * -----------------------------------------------------------------------------------------------------------------
+ */
+ public function merCateId()
+ {
+ return $this->hasMany(ProductCate::class,'product_id','product_id')->field('product_id,mer_cate_id');
+ }
+ public function attr()
+ {
+ return $this->hasMany(ProductAttr::class,'product_id','product_id');
+ }
+ public function attrValue()
+ {
+ return $this->hasMany(ProductAttrValue::class,'product_id','product_id');
+ }
+ public function oldAttrValue()
+ {
+ return $this->hasMany(ProductAttrValue::class,'product_id','old_product_id');
+ }
+ public function content()
+ {
+ return $this->hasOne(ProductContent::class,'product_id','product_id');
+ }
+ protected function temp()
+ {
+ return $this->hasOne(ShippingTemplate::class,'shipping_template_id','temp_id');
+ }
+ public function storeCategory()
+ {
+ return $this->hasOne(StoreCategory::class,'store_category_id','cate_id')->field('store_category_id,cate_name');
+ }
+ public function merchant()
+ {
+ return $this->hasOne(Merchant::class,'mer_id','mer_id')->field('is_trader,type_id,mer_id,mer_name,mer_avatar,product_score,service_score,postage_score,service_phone,care_count');
+ }
+ public function reply()
+ {
+ return $this->hasMany(ProductReply::class,'product_id','product_id')->order('create_time DESC');
+ }
+ public function brand()
+ {
+ return $this->hasOne(StoreBrand::class,'brand_id','brand_id')->field('brand_id,brand_name');
+ }
+ public function seckillActive()
+ {
+ return $this->hasOne(StoreSeckillActive::class,'product_id','product_id');
+ }
+ public function issetCoupon()
+ {
+ return $this->hasOne(StoreCouponProduct::class, 'product_id', 'product_id')->alias('A')
+ ->rightJoin('StoreCoupon B', 'A.coupon_id = B.coupon_id')->where(function (BaseQuery $query) {
+ $query->where('B.is_limited', 0)->whereOr(function (BaseQuery $query) {
+ $query->where('B.is_limited', 1)->where('B.remain_count', '>', 0);
+ });
+ })->where(function (BaseQuery $query) {
+ $query->where('B.is_timeout', 0)->whereOr(function (BaseQuery $query) {
+ $time = date('Y-m-d H:i:s');
+ $query->where('B.is_timeout', 1)->where('B.start_time', '<', $time)->where('B.end_time', '>', $time);
+ });
+ })->field('A.product_id,B.*')->where('status', 1)->where('type', 1)->where('send_type', 0)->where('is_del', 0)
+ ->order('sort DESC,coupon_id DESC')->hidden(['is_del', 'status']);
+ }
+ public function assist()
+ {
+ return $this->hasOne(ProductAssist::class,'product_id','product_id');
+ }
+ public function productGroup()
+ {
+ return $this->hasOne(ProductGroup::class,'product_id','product_id');
+ }
+ public function guarantee()
+ {
+ return $this->hasOne(GuaranteeTemplate::class,'guarantee_template_id','guarantee_template_id')->where('status',1)->where('is_del',0);
+ }
+
+
+ /**
+ * 更新商户商品
+ */
+ public function changeMerchantProduct($merId, $data)
+ {
+ return self::where('mer_id', $merId)->update($data);
+ }
+
+ /**
+ * TODO 是否是会员
+ * @return bool
+ * @author Qinii
+ * @day 2023/1/4
+ */
+ public function getIsVipAttr()
+ {
+ if (request()->isLogin()) {
+ if (request()->userType() == 1) {
+ $userInfo = request()->userInfo();
+ return $userInfo->is_svip ? true : false;
+ } else {
+ return true;
+ }
+ }
+ return false;
+ }
+ /**
+ * TODO 是否展示会员价
+ * @return bool
+ * @author Qinii
+ * @day 2023/1/4
+ */
+ public function getShowSvipPriceAttr()
+ {
+ if ($this->mer_svip_status != 0 && (systemConfig('svip_show_price') != 1 || $this->is_vip) && $this->svip_price_type > 0 ) {
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * TODO 是否显示会员价等信息
+ * @return array
+ * @author Qinii
+ * @day 2022/11/24
+ */
+ public function getShowSvipInfoAttr()
+ {
+ $res = [
+ 'show_svip' => true, //是否展示会员入口
+ 'is_svip' => false, //当前用户是否是会员
+ 'show_svip_price' => false, //是否展示会员价
+ 'save_money' => 0, //当前商品会员优化多少钱
+ ];
+ if ($this->product_type == 0) {
+ if (!systemConfig('svip_switch_status')) {
+ $res['show_svip'] = false;
+ } else {
+ $res['is_svip'] = $this->is_vip;
+ if ($this->show_svip_price) {
+ $res['show_svip_price'] = true;
+ $res['save_money'] = bcsub($this->price, $this->svip_price, 2);
+ }
+ }
+ }
+ return $res;
+ }
+
+ /**
+ * TODO 获取会员价
+ * @return int|string
+ * @author Qinii
+ * @day 2023/1/4
+ */
+ public function getSvipPriceAttr()
+ {
+ if ($this->product_type == 0 && $this->mer_svip_status != 0 && $this->show_svip_price) {
+ //默认比例
+ if ($this->svip_price_type == 1) {
+ $rate = merchantConfig($this->mer_id,'svip_store_rate');
+ $svip_store_rate = $rate > 0 ? bcdiv($rate,100,2) : 0;
+ $price = $this->attrValue()->order('price ASC')->value('price');
+ return bcmul($price,$svip_store_rate,2);
+ }
+ //自定义
+ if ($this->svip_price_type == 2) {
+ return $this->getData('svip_price');
+ }
+ }
+ return 0;
+ }
+
+
+ /*
+ * -----------------------------------------------------------------------------------------------------------------
+ * 搜索器
+ * -----------------------------------------------------------------------------------------------------------------
+ */
+ public function searchMerCateIdAttr($query, $value)
+ {
+ $cate_ids = (StoreCategory::where('path','like','%/'.$value.'/%'))->column('store_category_id');
+ $cate_ids[] = intval($value);
+ $product_id = ProductCate::whereIn('mer_cate_id',$cate_ids)->column('product_id');
+ $query->whereIn('Product.product_id',$product_id);
+ }
+ public function searchKeywordAttr($query, $value)
+ {
+ if (!$value) return;
+ if (is_numeric($value)) {
+ $query->whereLike("Product.store_name|Product.keyword|bar_code|Product.product_id", "%{$value}%");
+ } else {
+ $word = app()->make(VicWordService::class)->getWord($value);
+ $query->where(function ($query) use ($word, $value) {
+ foreach ($word as $item) {
+ $query->whereOr('Product.store_name|Product.keyword', 'LIKE', "%$item%");
+ }
+ $query->order(Db::raw('REPLACE(Product.store_name,\'' . $value . '\',\'\')'));
+ });
+ }
+ }
+ public function searchStatusAttr($query, $value)
+ {
+ if($value === -1){
+ $query->where('Product.status', 'in',[-1,-2]);
+ }else {
+ $query->where('Product.status',$value);
+ }
+ }
+ public function searchCateIdAttr($query, $value)
+ {
+ $query->where('cate_id',$value);
+ }
+ public function searchCateIdsAttr($query, $value)
+ {
+ $query->whereIn('cate_id',$value);
+ }
+ public function searchIsShowAttr($query, $value)
+ {
+ $query->where('is_show',$value);
+ }
+ public function searchPidAttr($query, $value)
+ {
+ $cateId = app()->make(StoreCategoryRepository::class)->allChildren(intval($value));
+ $query->whereIn('cate_id', $cateId);
+ }
+ public function searchStockAttr($query, $value)
+ {
+ $value ? $query->where('stock','<=', $value) : $query->where('stock', $value);
+ }
+ public function searchIsNewAttr($query, $value)
+ {
+ $query->where('is_new',$value);
+ }
+ public function searchPriceAttr($query, $value)
+ {
+ if(empty($value[0]) && !empty($value[1]))
+ $query->where('price','<',$value[1]);
+ if(!empty($value[0]) && empty($value[1]))
+ $query->where('price','>',$value[0]);
+ if(!empty($value[0]) && !empty($value[1]))
+ $query->whereBetween('price',[$value[0],$value[1]]);
+ }
+ public function searchBrandIdAttr($query, $value)
+ {
+ $query->whereIn('brand_id',$value);
+ }
+ public function searchIsGiftBagAttr($query, $value)
+ {
+ $query->where('is_gift_bag',$value);
+ }
+ public function searchIsGoodAttr($query, $value)
+ {
+ $query->where('is_good',$value);
+ }
+ public function searchIsUsedAttr($query, $value)
+ {
+ $query->where('is_used',$value);
+ }
+ public function searchProductTypeAttr($query, $value)
+ {
+ $query->where('Product.product_type',$value);
+ }
+ public function searchSeckillStatusAttr($query, $value)
+ {
+ $product_id = (new StoreSeckillActiveDao())->getStatus($value)->column('product_id');
+ $query->whereIn('Product.product_id',$product_id);
+ }
+ public function searchStoreNameAttr($query, $value)
+ {
+ $query->where('Product.store_name','like','%'.$value.'%');
+ }
+ public function searchMerStatusAttr($query, $value)
+ {
+ $query->where('mer_status',$value);
+ }
+ public function searchProductIdAttr($query, $value)
+ {
+ $query->where('Product.product_id',$value);
+ }
+ public function searchPriceOnAttr($query, $value)
+ {
+ $query->where('price','>=',$value);
+ }
+ public function searchPriceOffAttr($query, $value)
+ {
+ $query->where('price','<=',$value);
+ }
+ public function searchisFictiAttr($query, $value)
+ {
+ $query->where('type',$value);
+ }
+ public function searchGuaranteeTemplateIdAttr($query, $value)
+ {
+ $query->whereIn('guarantee_template_id',$value);
+ }
+ public function searchTempIdAttr($query, $value)
+ {
+ $query->whereIn('Product.temp_id',$value);
+ }
+}
diff --git a/app/common/model/merchant/store/product/ProductCopy copy.php b/app/common/model/merchant/store/product/ProductCopy copy.php
new file mode 100644
index 0000000..c10c1ff
--- /dev/null
+++ b/app/common/model/merchant/store/product/ProductCopy copy.php
@@ -0,0 +1,110 @@
+ 'sys',
+ 'num' => Common::systemConfig('copy_product_defaul'),
+ 'message' => '赠送次数',
+ ];
+ $this->add($data,$merId);
+ }
+ }
+
+
+ /**
+ * TODO 添加记录并修改数据
+ * @param $data
+ * @param $merId
+ * @author Qinii
+ * @day 2020-08-06
+ */
+ public function add($data,$merId)
+ {
+ $make = app()->make(Merchant::class);
+ $getOne = $make->get($merId);
+
+ switch ($data['type']) {
+ case 'mer_dump':
+ //nobreak;
+ case 'pay_dump':
+ $field = 'export_dump_num';
+ break;
+ case 'sys':
+ //nobreak;
+ //nobreak;
+ case 'pay_copy':
+ //nobreak;
+ case 'copy':
+ //nobreak;
+ $field = 'copy_product_num';
+ break;
+ default:
+ $field = 'copy_product_num';
+ break;
+ }
+
+
+ $number = $getOne[$field] + $data['num'];
+ $arr = [
+ 'type' => $data['type'],
+ 'num' => $data['num'],
+ 'info' => $data['info']??'' ,
+ 'mer_id'=> $merId,
+ 'message' => $data['message'] ?? '',
+ 'number' => ($number < 0) ? 0 : $number,
+ ];
+ Db::transaction(function()use($arr,$make,$field){
+ self::create($arr);
+ if ($arr['num'] < 0) {
+ $make->sumFieldNum($arr['mer_id'],$arr['num'],$field);
+ } else {
+ $make->addFieldNum($arr['mer_id'],$arr['num'],$field);
+ }
+ });
+ }
+
+ public function search(array $where)
+ {
+ return $this->getModel()::getDB()
+ ->when(isset($where['mer_id']) && $where['mer_id'] !== '',function($query)use($where){
+ $query->where('mer_id',$where['mer_id']);
+ })
+ ->when(isset($where['type']) && $where['type'] !== '',function($query)use($where){
+ if($where['type'] == 'copy'){
+ $query->where('type','in',['taobao','jd','copy']);
+ } else {
+ $query->where('type',$where['type']);
+ }
+ })
+ ->order('create_time DESC');
+ }
+
+}
diff --git a/app/common/model/merchant/system/financial/Financial.php b/app/common/model/merchant/system/financial/Financial.php
index 49bd57e..b723f17 100644
--- a/app/common/model/merchant/system/financial/Financial.php
+++ b/app/common/model/merchant/system/financial/Financial.php
@@ -11,9 +11,17 @@ declare (strict_types = 1);
namespace app\common\model\merchant\system\financial;
use think\Model;
+use think\facade\Db;
+use think\facade\Queue;
+use think\exception\ValidateException;
+
use app\common\model\merchant\system\admin\Admin;
use app\common\model\merchant\system\merchant\Merchant;
use app\common\model\merchant\system\merchant\MerchantAdmin;
+use app\common\model\merchant\system\serve\ServeOrder;
+use app\common\service\merchant\WechatService;
+use app\common\jobs\merchant\ChangeMerchantStatusJob;
+use think\db\exception\DbException;
/**
* 商户财务申请提现 model
@@ -25,7 +33,7 @@ class Financial extends Model
protected $pk = 'financial_id';
- // --------------
+ // -------------- depend function -----------------
public function getFinancialAccountAttr($value)
{
@@ -86,6 +94,39 @@ class Financial extends Model
return $this->hasOne(Merchant::class,'mer_id','mer_id');
}
+ /** ---------------------- depend func end */
+
+ /**
+ * 获取指定退还信息
+ */
+ public function get(int $id)
+ {
+ return self::where($this->getPk(), $id)->find();
+ }
+
+ public function modify($id, $data)
+ {
+ return self::where($this->getPk(), $id)->update($data);
+ }
+
+ /**
+ * 获取指定退款订单相关信息
+ * 此处依赖: getFinancialAccountAttr, merchant.marginOrder
+ *
+ */
+ public function getDetail($id)
+ {
+ $data = $this->get($id);
+ if (!$data['merchant']->marginOrder)
+ throw new ValidateException('未查询到缴费记录');
+ if ($data['status'] !== 0)
+ throw new ValidateException('请勿重复审核');
+ if (!$data['merchant']->merchantType)
+ throw new ValidateException('末查询到店铺类型');
+
+ return $data;
+ }
+
/**
* TODO 商户列表
* @param array $where
@@ -114,6 +155,56 @@ class Financial extends Model
return compact('count','list');
}
+ /**
+ * 退还保证金审核
+ */
+ public function switchStatus($id, $type, $data)
+ {
+ $where = [
+ 'financial_id' => $id,
+ 'is_del' => 0,
+ 'status' => 0,
+ 'type' => $type
+ ];
+
+ $res = self::where($where)->find();
+ if(!$res) throw new ValidateException('数据不存在');
+
+ switch ($type) {
+ case 0:
+ //
+ if ($data['status'] == -1)
+ $this->cancel(null,$id,$data);
+ break;
+ case 1:
+ //保证金
+ if ($data['status'] == 1) {
+ // 同意退回
+ $this->agree($res);
+ $data['financial_status'] = 1;
+ $tempId = 'REFUND_MARGIN_SUCCESS';
+ }else if ($data['status'] == -1) {
+ // 拒绝退回
+// $res->merchant->margin = $res['extract_money'];
+ $res->merchant->is_margin = -10;
+ $res->merchant->save();
+ $tempId = 'REFUND_MARGIN_FAIL';
+ }
+ // 发送模板消息
+ // Queue::push(SendSmsJob::class, [
+ // 'tempId' => $tempId,
+ // 'id' => [
+ // 'name' => $res->merchant->mer_name,
+ // 'time' => $res->create_time,
+ // 'phone' => $res->merchant->mer_phone
+ // ]]);
+ break;
+ }
+
+ return self::where($this->getPk(), $id)->update($data);
+ }
+
+
/**
* 组合sql条件
* @param array $where
@@ -134,42 +225,133 @@ class Financial extends Model
$query->where('is_del',0);
});
- $query->when(isset($where['status']) && $where['status'] !=='',function($query) use($where){
- $query->where('Financial.status',$where['status']);
- })
- ->when(isset($where['financial_type']) && $where['financial_type'] !=='',function($query) use($where){
- $query->where('Financial.financial_type',$where['financial_type']);
- })
- ->when(isset($where['mer_id']) && $where['mer_id'] !=='',function($query) use($where){
- $query->where('Financial.mer_id',$where['mer_id']);
- })
- ->when(isset($where['financial_status']) && $where['financial_status'] !=='',function($query) use($where){
- $query->where('Financial.financial_status',$where['financial_status']);
- })
- ->when(isset($where['keyword']) && $where['keyword'] !=='',function($query) use($where){
- $query->join('SystemAdmin A','Financial.admin_id = A.admin_id')
- ->field('A.real_name,A.admin_id,A.account')
- ->whereLike('A.real_name|A.account',"%{$where['keyword']}%");
- })
- ->when(isset($where['keywords_']) && $where['keywords_'] !=='',function($query) use($where){
- $query->join('MerchantAdmin M','Financial.mer_admin_id = M.merchant_admin_id')
- ->field('M.real_name,M.account,M.merchant_admin_id')
- ->whereLike('M.real_name|M.account',"%{$where['keywords_']}%");
- })
- ->when(isset($where['financial_id']) && $where['financial_id'] !=='',function($query) use($where){
- $query->where('Financial.financial_id',$where['financial_id']);
- })
- ->when(isset($where['date']) && $where['date'] !=='',function($query) use($where){
- getModelTime($query,$where['date'],'Financial.create_time');
- })
- ->when(isset($where['is_del']) && $where['is_del'] !=='',function($query) use($where){
- $query->where('Financial.is_del',$where['is_del']);
- })
- ->when(isset($where['type']) && $where['type'] !=='',function($query) use($where){
- $query->where('Financial.type',$where['type']);
- });;
+ $query->when(isset($where['status']) && $where['status'] !=='',
+ function($query) use($where){
+ $query->where('Financial.status',$where['status']);
+ })
+ ->when(isset($where['financial_type']) && $where['financial_type'] !=='',function($query) use($where){
+ $query->where('Financial.financial_type',$where['financial_type']);
+ })
+ ->when(isset($where['mer_id']) && $where['mer_id'] !=='',function($query) use($where){
+ $query->where('Financial.mer_id',$where['mer_id']);
+ })
+ ->when(isset($where['financial_status']) && $where['financial_status'] !=='',function($query) use($where){
+ $query->where('Financial.financial_status',$where['financial_status']);
+ })
+ ->when(isset($where['keyword']) && $where['keyword'] !=='',function($query) use($where){
+ $query->join('SystemAdmin A','Financial.admin_id = A.admin_id')
+ ->field('A.real_name,A.admin_id,A.account')
+ ->whereLike('A.real_name|A.account',"%{$where['keyword']}%");
+ })
+ ->when(isset($where['keywords_']) && $where['keywords_'] !=='',function($query) use($where){
+ $query->join('MerchantAdmin M','Financial.mer_admin_id = M.merchant_admin_id')
+ ->field('M.real_name,M.account,M.merchant_admin_id')
+ ->whereLike('M.real_name|M.account',"%{$where['keywords_']}%");
+ })
+ ->when(isset($where['financial_id']) && $where['financial_id'] !=='',function($query) use($where){
+ $query->where('Financial.financial_id',$where['financial_id']);
+ })
+ ->when(isset($where['date']) && $where['date'] !=='',function($query) use($where){
+ getModelTime($query,$where['date'],'Financial.create_time');
+ })
+ ->when(isset($where['is_del']) && $where['is_del'] !=='',function($query) use($where){
+ $query->where('Financial.is_del',$where['is_del']);
+ })
+ ->when(isset($where['type']) && $where['type'] !=='',function($query) use($where){
+ $query->where('Financial.type',$where['type']);
+ })
+ ->when(isset($where['start_date'])&&isset($where['end_date'])&&$where['start_date']!==''&&$where['end_date']!=='',
+ function($query)use($where){
+ $query->where('Financial.create_time','between',[$where['start_date'], $where['end_date']]);
+ });
$query->order('Financial.create_time DESC');
return $query;
}
+
+
+ /**
+ * TODO 取消/拒绝 变更状态返还余额
+ * @param $merId
+ * @param $id
+ * @param $data
+ */
+ protected function cancel(?int $merId,int $id,array $data)
+ {
+ $where = [
+ 'financial_id' => $id,
+ 'is_del' => 0,
+ 'status' => 0
+ ];
+ if($merId) $where['mer_id'] = $merId;
+ $res = self::where($where)->find();
+ if(!$res) throw new ValidateException('数据不存在');
+ if($res['financial_status'] == 1) throw new ValidateException('当前状态无法完成此操作');
+ $merId = $merId?? $res['mer_id'];
+ Db::transaction(function()use($merId,$res,$id,$data) {
+ self::where($this->getPk(), $id)->update($data);
+ app()->make(Merchant::class)->addMoney($merId, (float)$res['extract_money']);
+ });
+ }
+
+ /**
+ * TODO 同意退保证金
+ * @param $res
+ * @author Qinii
+ * @day 1/27/22
+ */
+ protected function agree($res)
+ {
+ //验证
+ $comp = bccomp($res['financial_account']->pay_price, $res['extract_money'], 2);
+ if ($comp == -1)
+ throw new ValidateException('申请退款金额:'.$res['extract_money'].',大于支付保证金:'.$res['financial_account']->pay_price);
+
+ // if (bccomp($res['merchant']['margin'], $res['extract_money'],2) == -1)
+ // throw new ValidateException('申请退款金额:'.$res['extract_money'].',大于剩余保证金:'.$res['merchant']['margin']);
+
+ Db::startTrans();
+ try{
+ //退款
+ $data = [
+ 'refund_id' => $res['financial_account']->order_sn,
+ 'pay_price' => $res['financial_account']->pay_price,
+ 'refund_price' => $res['extract_money']
+ ];
+ //退回微信
+ // WechatService::create()->payOrderRefund($res['financial_account']->order_sn, $data);
+
+ //改订单
+ $order = app()->make(ServeOrder::class)->get($res['financial_account']->order_id);
+ $order->status = 20;
+ $b1 = $order->save();
+
+ //关店 同时更新金额
+ $res->merchant->is_margin = $res['merchant']['merchantType']['is_margin'];
+ $res->merchant->margin = $res['merchant']['merchantType']['margin'];
+ if ($res['merchant']['merchantType']['is_margin'] == 1) {
+ $res->merchant->mer_state = 0;//商户关闭
+
+ //改变商户商品状态
+ // Queue::push(ChangeMerchantStatusJob::class, $res['mer_id']);
+ $b3 = app()->make(ChangeMerchantStatusJob::class)->fire([],$res['mer_id']);
+
+ }
+ $b2 = $res->merchant->save();
+ if (empty($b1)) {
+ Db::rollback();
+ throw new DbException('修改订单表失败');
+ }else if(empty($b2)) {
+ Db::rollback();
+ throw new DbException('更新商户表失败');
+ }else if(empty($b3)) {
+ Db::rollback();
+ throw new DbException('修改商户商品表失败');
+ }
+ Db::commit();
+ }catch(DbException $e){
+ Db::rollback();
+ throw new ValidateException($e->getMessage());
+ }
+ }
}
diff --git a/app/common/model/merchant/system/merchant/Merchant.php b/app/common/model/merchant/system/merchant/Merchant.php
index 1004fc7..f8948f3 100644
--- a/app/common/model/merchant/system/merchant/Merchant.php
+++ b/app/common/model/merchant/system/merchant/Merchant.php
@@ -71,6 +71,24 @@ class Merchant extends Model
->order('is_good DESC,sort DESC');
}
+ /**
+ * TODO 增加商户余额
+ * @param int $merId
+ * @param float $num
+ * @author Qinii
+ * @day 3/19/21
+ */
+ public function addMoney(int $merId, float $num)
+ {
+ $field = 'mer_money';
+ $merchant = self::where('mer_id', $merId)->find();
+ if ($merchant) {
+ $mer_money = bcadd($merchant[$field], (string)$num, 2);
+ $merchant[$field] = $mer_money;
+ $merchant->save();
+ }
+ }
+
/**
* TODO 商户列表下的推荐
* @return \think\Collection
diff --git a/app/common/model/merchant/system/serve/ServeOrder.php b/app/common/model/merchant/system/serve/ServeOrder.php
index 998de56..e52dcfb 100644
--- a/app/common/model/merchant/system/serve/ServeOrder.php
+++ b/app/common/model/merchant/system/serve/ServeOrder.php
@@ -36,6 +36,11 @@ class ServeOrder extends Model
{
return $this->hasOne(User::class,'mer_id','ud');
}
+
+ public function get(int $id)
+ {
+ return self::where($this->getPk(), $id)->find();
+ }
/**
* 获取所有商户的保证金数据
@@ -109,7 +114,7 @@ class ServeOrder extends Model
)
->when(isset($where['start_date'])&&isset($where['end_date'])&&$where['start_date']!==''&&$where['end_date']!=='',
function($query)use($where){
- $query->where('create_time','between',[$where['start_date'], $where['end_date']]);
+ $query->where('ServeOrder.create_time','between',[$where['start_date'], $where['end_date']]);
}
)
->when(isset($where['mer_id']) && $where['mer_id'] !== '',
diff --git a/app/common/service/easywechat/BaseClient.php b/app/common/service/easywechat/BaseClient.php
new file mode 100644
index 0000000..aeebf2c
--- /dev/null
+++ b/app/common/service/easywechat/BaseClient.php
@@ -0,0 +1,297 @@
+
+// +----------------------------------------------------------------------
+
+
+namespace app\common\services\easywechat;
+
+
+use EasyWeChat\Core\AbstractAPI;
+use EasyWeChat\Core\AccessToken;
+use EasyWeChat\Core\Exceptions\HttpException;
+use EasyWeChat\Core\Exceptions\InvalidConfigException;
+use EasyWeChat\Core\Http;
+use EasyWeChat\Encryption\EncryptionException;
+use think\exception\InvalidArgumentException;
+
+class BaseClient extends AbstractAPI
+{
+ protected $app;
+
+ const KEY_LENGTH_BYTE = 32;
+ const AUTH_TAG_LENGTH_BYTE = 16;
+
+ public function __construct(AccessToken $accessToken, $app)
+ {
+ parent::__construct($accessToken);
+ $this->app = $app;
+ }
+
+
+ /**
+ * @param $api
+ * @param $params
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ protected function httpPostJson($api, $params)
+ {
+ try {
+ return $this->parseJSON('json', [$api, $params]);
+ } catch (HttpException $e) {
+ $code = $e->getCode();
+ throw new HttpException("接口异常[$code]" . ($e->getMessage()), $code);
+ }
+ }
+
+ /**
+ * @param $api
+ * @param $params
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ protected function httpPost($api, $params)
+ {
+ try {
+ return $this->parseJSON('post', [$api, $params]);
+ } catch (HttpException $e) {
+ $code = $e->getCode();
+ throw new HttpException("接口异常[$code]" . ($e->getMessage()), $code);
+ }
+ }
+
+
+ /**
+ * @param $api
+ * @param $params
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ protected function httpGet($api, $params)
+ {
+ try {
+ return $this->parseJSON('get', [$api, $params]);
+ } catch (HttpException $e) {
+ $code = $e->getCode();
+ throw new HttpException("接口异常[$code]" . ($e->getMessage()), $code);
+ }
+ }
+
+ /**
+ * request.
+ *
+ * @param string $endpoint
+ * @param string $method
+ * @param array $options
+ * @param bool $returnResponse
+ */
+ public function request(string $endpoint, string $method = 'POST', array $options = [], $serial = true)
+ {
+ $sign_body = $options['sign_body'] ?? '';
+ $headers = [
+ 'Content-Type' => 'application/json',
+ 'User-Agent' => 'curl',
+ 'Accept' => 'application/json',
+ 'Authorization' => $this->getAuthorization($endpoint, $method, $sign_body),
+// 'Wechatpay-Serial' => $this->app['config']['payment']['serial_no']
+ ];
+ $options['headers'] = array_merge($headers, ($options['headers'] ?? []));
+
+ if ($serial) $options['headers']['Wechatpay-Serial'] = $this->app->certficates->get()['serial_no'];
+
+ Http::setDefaultOptions($options);
+ return $this->_doRequestCurl($method, 'https://api.mch.weixin.qq.com' . $endpoint, $options);
+ }
+
+
+ private function _doRequestCurl($method, $location, $options = [])
+ {
+ $curl = curl_init();
+ // POST数据设置
+ if (strtolower($method) === 'post') {
+ curl_setopt($curl, CURLOPT_POST, true);
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $options['data'] ?? $options['sign_body'] ?? '');
+ }
+ // CURL头信息设置
+ if (!empty($options['headers'])) {
+ $headers = [];
+ foreach ($options['headers'] as $k => $v) {
+ $headers[] = "$k: $v";
+ }
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
+ }
+ curl_setopt($curl, CURLOPT_URL, $location);
+ curl_setopt($curl, CURLOPT_HEADER, true);
+ curl_setopt($curl, CURLOPT_TIMEOUT, 60);
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
+ curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
+ $content = curl_exec($curl);
+ $headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
+ curl_close($curl);
+ return json_decode(substr($content, $headerSize), true);
+ }
+
+
+ /**
+ * get sensitive fields name.
+ *
+ * @return array
+ */
+ protected function getSensitiveFieldsName()
+ {
+ return [
+ 'contact_name',
+ 'contact_id_number',
+ 'mobile_phone',
+ 'contact_email',
+ 'id_card_name',
+ 'id_card_number',
+ 'id_card_address',
+ 'id_doc_name',
+ 'id_doc_number',
+ 'id_doc_address',
+ 'name',
+ 'id_number',
+ 'account_name',
+ 'account_number',
+ 'contact_id_card_number',
+ 'contact_email',
+ 'openid',
+ 'ubo_id_doc_name',
+ 'ubo_id_doc_number',
+ 'ubo_id_doc_address',
+ 'bank_address_code',
+ ];
+ }
+
+ /**
+ * To id card, mobile phone number and other fields sensitive information encryption.
+ *
+ * @param string $string
+ *
+ * @return string
+ */
+ protected function encryptSensitiveInformation(string $string)
+ {
+ $certificates = $this->app->certficates->get()['certificates'];
+ if (null === $certificates) {
+ throw new InvalidConfigException('config certificate connot be empty.');
+ }
+ $encrypted = '';
+ if (openssl_public_encrypt($string, $encrypted, $certificates, OPENSSL_PKCS1_OAEP_PADDING)) {
+ //base64编码
+ $sign = base64_encode($encrypted);
+ } else {
+ throw new EncryptionException('Encryption of sensitive information failed');
+ }
+ return $sign;
+ }
+
+ /**
+ * processing parameters contain fields that require sensitive information encryption.
+ *
+ * @param array $params
+ *
+ * @return array
+ */
+ protected function processParams(array $params)
+ {
+
+ $sensitive_fields = $this->getSensitiveFieldsName();
+ foreach ($params as $k => $v) {
+ if (is_array($v)) {
+ $params[$k] = $this->processParams($v);
+ } else {
+ if (in_array($k, $sensitive_fields, true)) {
+ $params[$k] = $this->encryptSensitiveInformation($v);
+ }
+ }
+ }
+
+ return $params;
+ }
+
+ /**
+ * @param string $url
+ * @param string $method
+ * @param string $body
+ * @return string
+ */
+ protected function getAuthorization(string $url, string $method, string $body)
+ {
+ $nonce_str = uniqid();
+ $timestamp = time();
+ $message = $method . "\n" .
+ $url . "\n" .
+ $timestamp . "\n" .
+ $nonce_str . "\n" .
+ $body . "\n";
+ openssl_sign($message, $raw_sign, $this->getPrivateKey(), 'sha256WithRSAEncryption');
+ $sign = base64_encode($raw_sign);
+ $schema = 'WECHATPAY2-SHA256-RSA2048 ';
+ $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
+ $this->app['config']['service_payment']['merchant_id'], $nonce_str, $timestamp, $this->app['config']['service_payment']['serial_no'], $sign);
+
+ return $schema . $token;
+ }
+
+ /**
+ * 获取商户私钥
+ * @return bool|resource
+ */
+ protected function getPrivateKey()
+ {
+ $key_path = $this->app['config']['service_payment']['key_path'];
+ if (!file_exists($key_path)) {
+ throw new \InvalidArgumentException(
+ "SSL certificate not found: {$key_path}"
+ );
+ }
+ return openssl_pkey_get_private(file_get_contents($key_path));
+ }
+
+ /**
+ * decrypt ciphertext.
+ *
+ * @param array $encryptCertificate
+ *
+ * @return string
+ */
+ public function decrypt(array $encryptCertificate)
+ {
+ $ciphertext = base64_decode($encryptCertificate['ciphertext'], true);
+ $associatedData = $encryptCertificate['associated_data'];
+ $nonceStr = $encryptCertificate['nonce'];
+ $aesKey = $this->app['config']['service_payment']['apiv3_key'];
+
+ try {
+ // ext-sodium (default installed on >= PHP 7.2)
+ if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') && \sodium_crypto_aead_aes256gcm_is_available()) {
+ return \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $aesKey);
+ }
+ // ext-libsodium (need install libsodium-php 1.x via pecl)
+ if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') && \Sodium\crypto_aead_aes256gcm_is_available()) {
+ return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $aesKey);
+ }
+ // openssl (PHP >= 7.1 support AEAD)
+ if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) {
+ $ctext = substr($ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE);
+ $authTag = substr($ciphertext, -self::AUTH_TAG_LENGTH_BYTE);
+ return \openssl_decrypt($ctext, 'aes-256-gcm', $aesKey, \OPENSSL_RAW_DATA, $nonceStr, $authTag, $associatedData);
+ }
+ } catch (\Exception $exception) {
+ throw new InvalidArgumentException($exception->getMessage(), $exception->getCode());
+ } catch (\SodiumException $exception) {
+ throw new InvalidArgumentException($exception->getMessage(), $exception->getCode());
+ }
+ throw new InvalidArgumentException('AEAD_AES_256_GCM 需要 PHP 7.1 以上或者安装 libsodium-php');
+ }
+}
diff --git a/app/common/service/easywechat/broadcast/Client.php b/app/common/service/easywechat/broadcast/Client.php
new file mode 100644
index 0000000..ca35139
--- /dev/null
+++ b/app/common/service/easywechat/broadcast/Client.php
@@ -0,0 +1,462 @@
+
+// +----------------------------------------------------------------------
+
+
+namespace crmeb\services\easywechat\broadcast;
+
+
+use EasyWeChat\Core\Exceptions\HttpException;
+use EasyWeChat\MiniProgram\Core\AbstractMiniProgram;
+
+/**
+ * Class Client.
+ *
+ * @author Abbotton
+ */
+class Client extends AbstractMiniProgram
+{
+ const MSG_CODE = [
+ '1' => '未创建直播间',
+ '1003' => '商品id不存在',
+ '47001' => '入参格式不符合规范',
+ '200002' => '入参错误',
+ '300001' => '禁止创建/更新商品 或 禁止编辑&更新房间',
+ '300002' => '名称长度不符合规则',
+ '300006' => '图片上传失败',
+ '300022' => '此房间号不存在',
+ '300023' => '房间状态 拦截',
+ '300024' => '商品不存在',
+ '300025' => '商品审核未通过',
+ '300026' => '房间商品数量已经满额',
+ '300027' => '导入商品失败',
+ '300028' => '房间名称违规',
+ '300029' => '主播昵称违规',
+ '300030' => '主播微信号不合法',
+ '300031' => '直播间封面图不合规',
+ '300032' => '直播间分享图违规',
+ '300033' => '添加商品超过直播间上限',
+ '300034' => '主播微信昵称长度不符合要求',
+ '300035' => '主播微信号不存在',
+ '300003' => '价格输入不合规',
+ '300004' => '商品名称存在违规违法内容',
+ '300005' => '商品图片存在违规违法内容',
+ '300007' => '线上小程序版本不存在该链接',
+ '300008' => '添加商品失败',
+ '300009' => '商品审核撤回失败',
+ '300010' => '商品审核状态不对',
+ '300011' => '操作非法',
+ '300012' => '没有提审额度',
+ '300013' => '提审失败',
+ '300014' => '审核中,无法删除',
+ '300017' => '商品未提审',
+ '300018' => '图片尺寸不符合要求',
+ '300021' => '商品添加成功,审核失败',
+ '300036' => '请先在微信直播小程序中实名认证',
+ '300038' => '请先在小程序后台配置直播客服',
+ '-1' => '系统错误',
+ ];
+
+ const API = 'https://api.weixin.qq.com/';
+
+ /**
+ * @param $api
+ * @param $params
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ protected function httpPostJson($api, $params)
+ {
+ try {
+ return $this->parseJSON('json', [self::API . $api, $params]);
+ } catch (HttpException $e) {
+ $code = $e->getCode();
+ throw new HttpException("接口异常[$code]" . (self::MSG_CODE[$code] ?? $e->getMessage()), $code);
+ }
+ }
+
+ /**
+ * @param $api
+ * @param $params
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ protected function httpPost($api, $params)
+ {
+ try {
+ return $this->parseJSON('post', [self::API . $api, $params]);
+ } catch (HttpException $e) {
+ $code = $e->getCode();
+ throw new HttpException("接口异常[$code]" . (self::MSG_CODE[$code] ?? $e->getMessage()), $code);
+ }
+ }
+
+
+ /**
+ * @param $api
+ * @param $params
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ protected function httpGet($api, $params)
+ {
+ try {
+ return $this->parseJSON('get', [self::API . $api, $params]);
+ } catch (HttpException $e) {
+ $code = $e->getCode();
+ throw new HttpException("接口异常[$code]" . (self::MSG_CODE[$code] ?? $e->getMessage()), $code);
+ }
+ }
+
+ /**
+ * Add broadcast goods.
+ *
+ * @param array $goodsInfo
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ public function create(array $goodsInfo)
+ {
+ $params = [
+ 'goodsInfo' => $goodsInfo,
+ ];
+
+ return $this->httpPostJson('wxaapi/broadcast/goods/add', $params);
+ }
+
+ /**
+ * Reset audit.
+ *
+ * @param int $auditId
+ * @param int $goodsId
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ public function resetAudit(int $auditId, int $goodsId)
+ {
+ $params = [
+ 'auditId' => $auditId,
+ 'goodsId' => $goodsId,
+ ];
+
+ return $this->httpPostJson('wxaapi/broadcast/goods/resetaudit', $params);
+ }
+
+ /**
+ * Resubmit audit goods.
+ *
+ * @param int $goodsId
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ public function resubmitAudit(int $goodsId)
+ {
+ $params = [
+ 'goodsId' => $goodsId,
+ ];
+
+ return $this->httpPostJson('wxaapi/broadcast/goods/audit', $params);
+ }
+
+ /**
+ * Delete broadcast goods.
+ *
+ * @param int $goodsId
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ public function delete(int $goodsId)
+ {
+ $params = [
+ 'goodsId' => $goodsId,
+ ];
+ try{
+ return $this->httpPostJson('wxaapi/broadcast/goods/delete', $params);
+ } catch (HttpException $exception) {
+ if ($exception->getCode() == 300015) return ;
+ }
+ }
+
+ /**
+ * Update goods info.
+ *
+ * @param array $goodsInfo
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ public function update(array $goodsInfo)
+ {
+ $params = [
+ 'goodsInfo' => $goodsInfo,
+ ];
+
+ return $this->httpPostJson('wxaapi/broadcast/goods/update', $params);
+ }
+
+ /**
+ * Get goods information and review status.
+ *
+ * @param array $goodsIdArray
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ public function getGoodsWarehouse(array $goodsIdArray)
+ {
+ $params = [
+ 'goods_ids' => $goodsIdArray,
+ ];
+
+ return $this->httpPostJson('wxa/business/getgoodswarehouse', $params);
+ }
+
+ /**
+ * Get goods list based on status
+ *
+ * @param array $params
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ public function getApproved(array $params)
+ {
+ return $this->httpGet('wxaapi/broadcast/goods/getapproved', $params);
+ }
+
+ /**
+ * Add goods to the designated live room.
+ *
+ * @param array $params
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ public function addGoods(array $params)
+ {
+ return $this->httpPost('wxaapi/broadcast/room/addgoods', $params);
+ }
+
+ /**
+ * Get Room List.
+ *
+ * @param int $start
+ * @param int $limit
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ public function getRooms(int $start = 0, int $limit = 10)
+ {
+ $params = [
+ 'start' => $start,
+ 'limit' => $limit,
+ ];
+
+ return $this->httpPostJson('wxa/business/getliveinfo', $params);
+ }
+
+ /**
+ * Get Playback List.
+ *
+ * @param int $roomId
+ * @param int $start
+ * @param int $limit
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ public function getPlaybacks(int $roomId, int $start = 0, int $limit = 10)
+ {
+ $params = [
+ 'action' => 'get_replay',
+ 'room_id' => $roomId,
+ 'start' => $start,
+ 'limit' => $limit,
+ ];
+
+ return $this->httpPostJson('wxa/business/getliveinfo', $params);
+ }
+
+ /**
+ * Create a live room.
+ *
+ * @param array $params
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ public function createLiveRoom(array $params)
+ {
+ return $this->httpPostJson('wxaapi/broadcast/room/create', $params);
+ }
+
+ /**
+ * TODO
+ * @param int $roomId
+ * @return \EasyWeChat\Support\Collection|null
+ * @author Qinii
+ * @day 10/22/21
+ */
+ public function getPushUrl(int $roomId)
+ {
+ $params = [
+ 'roomId' => $roomId,
+ ];
+ return $this->httpGet('wxaapi/broadcast/room/getpushurl', $params);
+ }
+
+ /**
+ * TODO 是否关闭客服 【0:开启,1:关闭】
+ * @param int $roomId
+ * @return \EasyWeChat\Support\Collection|null
+ * @author Qinii
+ * @day 10/22/21
+ */
+ public function closeKf(int $roomId,int $status)
+ {
+ $params = [
+ 'roomId' => $roomId,
+ 'closeKf' => $status ? 1 : 0,
+ ];
+ return $this->httpPostJson('wxaapi/broadcast/room/updatekf', $params);
+ }
+
+ /**
+ * TODO 1-禁言,0-取消禁言
+ * @param int $roomId
+ * @param int $type
+ * @return \EasyWeChat\Support\Collection|null
+ * @author Qinii
+ * @day 10/22/21
+ */
+ public function banComment(int $roomId, int $status)
+ {
+ $params = [
+ 'roomId' => $roomId,
+ 'banComment' => $status ? 1 : 0,
+ ];
+ return $this->httpPostJson('wxaapi/broadcast/room/updatecomment', $params);
+ }
+
+ /**
+ * TODO 添加助手
+ * @param array $params
+ * @return \EasyWeChat\Support\Collection|null
+ * @author Qinii
+ * @day 10/25/21
+ */
+ public function addAssistant(array $params)
+ {
+ return $this->httpPostJson('wxaapi/broadcast/room/addassistant', $params);
+ }
+
+ /**
+ * TODO 删除助手
+ * @param array $params
+ * @return \EasyWeChat\Support\Collection|null
+ * @author Qinii
+ * @day 10/25/21
+ */
+ public function removeAssistant(int $roomId, string $username)
+ {
+ $params = [
+ 'roomId' => $roomId,
+ 'username' => $username,
+ ];
+ return $this->httpPostJson('wxaapi/broadcast/room/removeassistant', $params);
+ }
+
+ /**
+ * TODO 修改小助手
+ * @param array $params
+ * @return \EasyWeChat\Support\Collection|null
+ * @author Qinii
+ * @day 10/25/21
+ */
+ public function modifyAssistant(array $params)
+ {
+ return $this->httpPostJson('wxaapi/broadcast/room/modifyassistant', $params);
+ }
+
+ /**
+ * TODO 助手列表
+ * @param int $roomId
+ * @return \EasyWeChat\Support\Collection|null
+ * @author Qinii
+ * @day 10/25/21
+ * wxa/business/get_wxa_followers?access_token=
+
+
+ */
+ public function getAssistantList(int $roomId)
+ {
+ $params = [
+ 'roomId' => $roomId,
+ ];
+ return $this->httpGet('wxaapi/broadcast/room/getassistantlist', $params);
+ }
+
+ /**
+ * TODO 获取长期订阅用户
+ * @param int $roomId
+ * @return \EasyWeChat\Support\Collection|null
+ * @author Qinii
+ * @day 10/25/21
+ */
+ public function getFollowers(string $page, int $limit = 2000)
+ {
+ $params['limit'] = $limit;
+ if ($page) $params['page_break'] = $page;
+ return $this->httpPostJson('wxa/business/get_wxa_followers', $params);
+ }
+
+ /**
+ * TODO 群发发送订阅
+ * @param int $roomId
+ * @param array $data
+ * @return \EasyWeChat\Support\Collection|null
+ * @author Qinii
+ * @day 10/25/21
+ */
+ public function pushMessage(int $roomId, array $data)
+ {
+ $params = [
+ 'room_id' => $roomId,
+ 'user_openid' => $data,
+ ];
+
+ return $this->httpPostJson('wxa/business/push_message', $params);
+ }
+
+
+ /**
+ * TODO 更新官方收录
+ * @param int $roomId
+ * @param int $status
+ * @return \EasyWeChat\Support\Collection|null
+ * @author Qinii
+ * @day 10/30/21
+ */
+ public function updateFeedPublic(int $roomId, int $status)
+ {
+ $params = [
+ 'roomId' => $roomId,
+ 'isFeedsPublic' => $status ? 1 : 0,
+ ];
+
+ return $this->httpPostJson('wxaapi/broadcast/room/updatefeedpublic', $params);
+ }
+
+ public function goodsOnsale(int $roomId, int $goodsId, int $status)
+ {
+ $params = [
+ 'roomId' => $roomId,
+ 'goodsId' => $goodsId,
+ 'onSale' => $status ? 1 : 0,
+ ];
+
+ return $this->httpPostJson('wxaapi/broadcast/goods/onsale', $params);
+ }
+}
diff --git a/app/common/service/easywechat/broadcast/ServiceProvider.php b/app/common/service/easywechat/broadcast/ServiceProvider.php
new file mode 100644
index 0000000..8a8a36d
--- /dev/null
+++ b/app/common/service/easywechat/broadcast/ServiceProvider.php
@@ -0,0 +1,29 @@
+
+// +----------------------------------------------------------------------
+
+
+namespace crmeb\services\easywechat\broadcast;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+
+ public function register(Container $pimple)
+ {
+ $pimple['miniBroadcast'] = function ($pimple) {
+ return new Client($pimple['mini_program.access_token'], $pimple['config']['mini_program']);
+ };
+ \EasyWeChat\Core\Http::setDefaultOptions(['timeout' => 0]);
+ }
+}
diff --git a/app/common/service/easywechat/certficates/Client.php b/app/common/service/easywechat/certficates/Client.php
new file mode 100644
index 0000000..2d56de7
--- /dev/null
+++ b/app/common/service/easywechat/certficates/Client.php
@@ -0,0 +1,50 @@
+
+// +----------------------------------------------------------------------
+
+
+namespace crmeb\services\easywechat\certficates;
+
+
+use crmeb\exceptions\WechatException;
+use crmeb\services\easywechat\BaseClient;
+use EasyWeChat\Core\AbstractAPI;
+use think\exception\InvalidArgumentException;
+use think\facade\Cache;
+
+class Client extends BaseClient
+{
+ public function get()
+ {
+ $driver = Cache::store('file');
+ $cacheKey = '_wx_v3' . $this->app['config']['service_payment']['serial_no'];
+ if ($driver->has($cacheKey)) {
+ return $driver->get($cacheKey);
+ }
+ $certficates = $this->getCertficates();
+ $driver->set($cacheKey, $certficates, 3600 * 24 * 30);
+ return $certficates;
+ }
+
+ /**
+ * get certficates.
+ *
+ * @return array
+ */
+ public function getCertficates()
+ {
+ $response = $this->request('/v3/certificates', 'GET', [], false);
+ if (isset($response['code'])) throw new WechatException($response['message']);
+ $certificates = $response['data'][0];
+ $certificates['certificates'] = $this->decrypt($certificates['encrypt_certificate']);
+ unset($certificates['encrypt_certificate']);
+ return $certificates;
+ }
+}
diff --git a/app/common/service/easywechat/certficates/ServiceProvider.php b/app/common/service/easywechat/certficates/ServiceProvider.php
new file mode 100644
index 0000000..9d8bf58
--- /dev/null
+++ b/app/common/service/easywechat/certficates/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace crmeb\services\easywechat\certficates;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author ClouderSky
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $pimple)
+ {
+ $pimple['certficates'] = function ($pimple) {
+ return new Client($pimple['access_token'], $pimple);
+ };
+ }
+}
diff --git a/app/common/service/easywechat/combinePay/Client.php b/app/common/service/easywechat/combinePay/Client.php
new file mode 100644
index 0000000..d42ac3f
--- /dev/null
+++ b/app/common/service/easywechat/combinePay/Client.php
@@ -0,0 +1,265 @@
+
+// +----------------------------------------------------------------------
+
+
+namespace crmeb\services\easywechat\combinePay;
+
+
+use app\common\model\store\order\StoreRefundOrder;
+use crmeb\services\easywechat\BaseClient;
+use think\exception\ValidateException;
+use think\facade\Route;
+use function EasyWeChat\Payment\generate_sign;
+
+class Client extends BaseClient
+{
+
+ public function handleNotify($callback)
+ {
+ $request = request();
+ $success = $request->post('event_type') === 'TRANSACTION.SUCCESS';
+ $data = $this->decrypt($request->post('resource', []));
+
+ $handleResult = call_user_func_array($callback, [json_decode($data, true), $success]);
+ if (is_bool($handleResult) && $handleResult) {
+ $response = [
+ 'code' => 'SUCCESS',
+ 'message' => 'OK',
+ ];
+ } else {
+ $response = [
+ 'code' => 'FAIL',
+ 'message' => $handleResult,
+ ];
+ }
+
+ return response($response, 200, [], 'json');
+ }
+
+ public function pay($type, array $order)
+ {
+ $params = [
+ 'combine_out_trade_no' => $order['order_sn'],
+ 'combine_mchid' => $this->app['config']['service_payment']['merchant_id'],
+ 'combine_appid' => $this->app['config']['app_id'],
+ 'scene_info' => [
+ 'device_id' => 'shop system',
+ 'payer_client_ip' => request()->ip(),
+ ],
+ 'sub_orders' => [],
+ 'notify_url' => rtrim(systemConfig('site_url'), '/') . Route::buildUrl($this->app['config']['service_payment']['type'] . 'CombinePayNotify', ['type' => $order['attach']])->build(),
+ ];
+
+ if ($type === 'h5') {
+ $params['scene_info']['h5_info'] = [
+ 'type' => $order['h5_type'] ?? 'Wap'
+ ];
+ }
+
+ foreach ($order['sub_orders'] as $sub_order) {
+ $subOrder = [
+ 'mchid' => $this->app['config']['service_payment']['merchant_id'],
+ 'amount' => [
+ 'total_amount' => intval($sub_order['pay_price'] * 100),
+ 'currency' => 'CNY',
+ ],
+ 'settle_info' => [
+ 'profit_sharing' => true
+ ],
+ 'out_trade_no' => $sub_order['order_sn'],
+ 'sub_mchid' => $sub_order['sub_mchid']
+ ];
+ $subOrder['attach'] = $sub_order['attach'] ?? $order['attach'] ?? '';
+ $subOrder['description'] = $sub_order['body'] ?? $order['body'] ?? '';
+ $params['sub_orders'][] = $subOrder;
+ }
+
+ if (isset($order['openid'])) {
+ $params['combine_payer_info'] = [
+ 'openid' => $order['openid'],
+ ];
+ }
+ $content = json_encode($params, JSON_UNESCAPED_UNICODE);
+
+ $res = $this->request('/v3/combine-transactions/' . $type, 'POST', ['sign_body' => $content]);
+ if (isset($res['code'])) {
+ throw new ValidateException('微信接口报错:' . $res['message']);
+ }
+ return $res;
+ }
+
+ public function payApp(array $options)
+ {
+ $res = $this->pay('app', $options);
+ return $this->configForAppPayment($res['prepay_id']);
+ }
+
+ /**
+ * @param string $type 场景类型,枚举值: iOS:IOS移动应用; Android:安卓移动应用; Wap:WAP网站应用
+ */
+ public function payH5(array $options, $type = 'Wap')
+ {
+ $options['h5_type'] = $type;
+ return $this->pay('h5', $options);
+ }
+
+ public function payJs($openId, array $options)
+ {
+ $options['openid'] = $openId;
+ $res = $this->pay('jsapi', $options);
+ return $this->configForJSSDKPayment($res['prepay_id']);
+ }
+
+ public function payNative(array $options)
+ {
+ return $this->pay('native', $options);
+ }
+
+ public function profitsharingOrder(array $options, bool $finish = false)
+ {
+ $params = [
+ 'appid' => $this->app['config']['app_id'],
+ 'sub_mchid' => $options['sub_mchid'],
+ 'transaction_id' => $options['transaction_id'],
+ 'out_order_no' => $options['out_order_no'],
+ 'receivers' => [],
+ 'finish' => $finish
+ ];
+
+ foreach ($options['receivers'] as $receiver) {
+ $data = [
+ 'amount' => intval($receiver['amount'] * 100),
+ 'description' => $receiver['body'] ?? $options['body'] ?? '',
+ ];
+ $data['receiver_account'] = $receiver['receiver_account'];
+ if (isset($receiver['receiver_name'])) {
+ $data['receiver_name'] = $receiver['receiver_name'];
+ $data['type'] = 'PERSONAL_OPENID';
+ } else {
+ $data['type'] = 'MERCHANT_ID';
+ }
+ $params['receivers'][] = $data;
+ }
+ $content = json_encode($params);
+ $res = $this->request('/v3/ecommerce/profitsharing/orders', 'POST', ['sign_body' => $content]);
+ if (isset($res['code'])) {
+ throw new ValidateException('微信接口报错:' . $res['message']);
+ }
+ return $res;
+ }
+
+ public function profitsharingFinishOrder(array $params)
+ {
+ $content = json_encode($params);
+ $res = $this->request('/v3/ecommerce/profitsharing/finish-order', 'POST', ['sign_body' => $content]);
+ if (isset($res['code'])) {
+ throw new ValidateException('微信接口报错:' . $res['message']);
+ }
+ return $res;
+ }
+
+ public function payOrderRefund(string $order_sn, array $options)
+ {
+ $params = [
+ 'sub_mchid' => $options['sub_mchid'],
+ 'sp_appid' => $this->app['config']['app_id'],
+ 'out_trade_no' => $options['order_sn'],
+ 'out_refund_no' => $options['refund_order_sn'],
+ 'amount' => [
+ 'refund' => intval($options['refund_price'] * 100),
+ 'total' => intval($options['pay_price'] * 100),
+ 'currency' => 'CNY'
+ ]
+ ];
+ if (isset($options['reason'])) {
+ $params['reason'] = $options['reason'];
+ }
+ if (isset($options['refund_account'])) {
+ $params['refund_account'] = $options['refund_account'];
+ }
+ $content = json_encode($params);
+ $res = $this->request('/v3/ecommerce/refunds/apply', 'POST', ['sign_body' => $content], true);
+ if (isset($res['code'])) {
+ throw new ValidateException('微信接口报错:' . $res['message']);
+ }
+ return $res;
+ }
+
+ public function returnAdvance($refund_id, $sub_mchid)
+ {
+ $res = $this->request('/v3/ecommerce/refunds/' . $refund_id . '/return-advance', 'POST', ['sign_body' => json_encode(compact('sub_mchid'))], true);
+ if (isset($res['code'])) {
+ throw new ValidateException('微信接口报错:' . $res['message']);
+ }
+ return $res;
+ }
+
+ public function configForPayment($prepayId, $json = true)
+ {
+ $params = [
+ 'appId' => $this->app['config']['app_id'],
+ 'timeStamp' => strval(time()),
+ 'nonceStr' => uniqid(),
+ 'package' => "prepay_id=$prepayId",
+ 'signType' => 'RSA',
+ ];
+ $message = $params['appId'] . "\n" .
+ $params['timeStamp'] . "\n" .
+ $params['nonceStr'] . "\n" .
+ $params['package'] . "\n";
+ openssl_sign($message, $raw_sign, $this->getPrivateKey(), 'sha256WithRSAEncryption');
+ $sign = base64_encode($raw_sign);
+
+ $params['paySign'] = $sign;
+
+ return $json ? json_encode($params) : $params;
+ }
+
+ /**
+ * Generate app payment parameters.
+ *
+ * @param string $prepayId
+ *
+ * @return array
+ */
+ public function configForAppPayment($prepayId)
+ {
+ $params = [
+ 'appid' => $this->app['config']['app_id'],
+ 'partnerid' => $this->app['config']['service_payment']['merchant_id'],
+ 'prepayid' => $prepayId,
+ 'noncestr' => uniqid(),
+ 'timestamp' => time(),
+ 'package' => 'Sign=WXPay',
+ ];
+ $message = $params['appid'] . "\n" .
+ $params['timestamp'] . "\n" .
+ $params['noncestr'] . "\n" .
+ $params['prepayid'] . "\n";
+ openssl_sign($message, $raw_sign, $this->getPrivateKey(), 'sha256WithRSAEncryption');
+ $sign = base64_encode($raw_sign);
+
+ $params['sign'] = $sign;
+
+ return $params;
+ }
+
+ public function configForJSSDKPayment($prepayId)
+ {
+ $config = $this->configForPayment($prepayId, false);
+
+ $config['timestamp'] = $config['timeStamp'];
+ unset($config['timeStamp']);
+
+ return $config;
+ }
+
+}
diff --git a/app/common/service/easywechat/combinePay/ServiceProvider.php b/app/common/service/easywechat/combinePay/ServiceProvider.php
new file mode 100644
index 0000000..5fd34e1
--- /dev/null
+++ b/app/common/service/easywechat/combinePay/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace crmeb\services\easywechat\combinePay;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author ClouderSky
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $pimple)
+ {
+ $pimple['combinePay'] = function ($pimple) {
+ return new Client($pimple['access_token'], $pimple);
+ };
+ }
+}
diff --git a/app/common/service/easywechat/merchant/Client.php b/app/common/service/easywechat/merchant/Client.php
new file mode 100644
index 0000000..2a557ce
--- /dev/null
+++ b/app/common/service/easywechat/merchant/Client.php
@@ -0,0 +1,217 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace crmeb\services\easywechat\merchant;
+
+use crmeb\services\easywechat\BaseClient;
+use EasyWeChat\Core\AbstractAPI;
+use EasyWeChat\Core\AccessToken;
+use EasyWeChat\Core\Exceptions\HttpException;
+use EasyWeChat\Core\Http;
+use EasyWeChat\Payment\API;
+use EasyWeChat\Payment\Merchant;
+use GuzzleHttp\HandlerStack;
+use think\Exception;
+use EasyWeChat\Support\XML;
+use EasyWeChat\Support\Collection;
+use Psr\Http\Message\ResponseInterface;
+use think\exception\ValidateException;
+
+/**
+ * Class Client.
+ *
+ * @author ClouderSky
+ */
+class Client extends BaseClient
+{
+
+ /**
+ * TODO 二级商户进件成为微信支付商户
+ * @param $params
+ * @return mixed
+ * @author Qinii
+ * @day 6/24/21
+ */
+ public function submitApplication($params)
+ {
+ $params = $this->processParams($params);
+ $res = $this->request('/v3/ecommerce/applyments/', 'POST', ['sign_body' => json_encode($params, JSON_UNESCAPED_UNICODE)], true);
+ if(isset($res['code'])) throw new ValidateException('[微信接口返回]:' . $res['message']);
+
+ return $res;
+ }
+
+ /**
+ * TODO 申请单ID查询申请状态
+ * @param $id
+ * @return mixed
+ * @author Qinii
+ * @day 6/24/21
+ */
+ public function getApplicationById($id)
+ {
+ $url = '/v3/ecommerce/applyments/'.$id;
+ $res = $this->request($url, 'GET');
+
+ if(isset($res['code'])) throw new ValidateException('[微信接口返回]:' . $res['message']);
+
+ return $res;
+ }
+
+ /**
+ * TODO 业务申请编号查询申请状
+ * @param $no
+ * @return mixed
+ * @author Qinii
+ * @day 6/24/21
+ */
+ public function getApplicationByNo($no)
+ {
+ $url = '/v3/ecommerce/applyments/out-request-no/'.$no;
+ $res = $this->request($url, 'GET');
+
+ if(isset($res['code'])) throw new ValidateException('[微信接口返回]:' . $res['message']);
+
+ return $res;
+ }
+
+ /**
+ * TODO 修改结算账号
+ * @param $mchid
+ * @param $params
+ * @return mixed
+ * @author Qinii
+ * @day 6/24/21
+ */
+ public function updateSubMerchat($mchid,$params)
+ {
+ $url = "/v3/apply4sub/sub_merchants/{$mchid}/modify-settlement";
+ $res = $this->request($url, 'POST',['sign_body' => json_encode($params, JSON_UNESCAPED_UNICODE)], true);
+ if(isset($res['code'])) throw new ValidateException('[微信接口返回]:' . $res['message']);
+
+ return $res;
+ }
+
+ /**
+ * TODO 查询结算账户
+ * @param $mchid
+ * @return mixed
+ * @author Qinii
+ * @day 6/24/21
+ */
+ public function getSubMerchant($mchid)
+ {
+ $url = "/v3/apply4sub/sub_merchants/{$mchid}/settlement";
+ $res = $this->request($url, 'GET');
+ if(isset($res['code'])) throw new ValidateException('[微信接口返回]:' . $res['message']);
+
+ return $res;
+ }
+
+ /**
+ * TODO 添加分账接收方
+ * @param array $params
+ * @return mixed
+ * @author Qinii
+ * @day 6/24/21
+ */
+ public function profitsharingAdd(array $params)
+ {
+ $url = '/v3/ecommerce/profitsharing/receivers/add';
+
+ $app_id = !empty($this->app->config->app_id) ? $this->app->config->app_id : $this->app->config->routine_appId;
+
+ $params['appid'] = $app_id;
+
+ $options['sign_body'] = json_encode($params,JSON_UNESCAPED_UNICODE);
+
+ $res = $this->request($url, 'POST',$options,true);
+
+ if(isset($res['code'])) throw new ValidateException('[微信接口返回]:' . $res['message']);
+
+ return $res;
+ }
+
+ /**
+ * TODO 删除分账接收方
+ * @param array $params
+ * @return mixed
+ * @author Qinii
+ * @day 6/24/21
+ */
+ public function profitsharingDel(array $params)
+ {
+ $url = '/v3/ecommerce/profitsharing/receivers/delete';
+
+ $app_id = !empty($this->app->config->app_id) ? $this->app->config->app_id : $this->app->config->routine_appId;
+
+ $params['appid'] = $app_id;
+
+ $options['sign_body'] = json_encode($params,JSON_UNESCAPED_UNICODE);
+
+ $res = $this->request($url, 'POST',$options,true);
+
+ if(isset($res['code'])) throw new ValidateException('[微信接口返回]:' . $res['message']);
+
+ return $res;
+ }
+
+ /**
+ * TODO 上传图片
+ * @param $filepath
+ * @param $filename
+ * @author Qinii
+ * @day 6/21/21
+ */
+ public function upload($filepath,$filename)
+ {
+
+ $boundary = uniqid();
+ try{
+ // $file = file_get_contents($filepath);
+ $file = fread(fopen($filepath,'r'),filesize($filepath));
+ }catch (\Exception $exception){
+ throw new ValidateException($exception->getMessage());
+ }
+
+
+ $options['headers'] = ['Content-Type' => 'multipart/form-data;boundary='.$boundary];
+
+ $options['sign_body'] = json_encode(['filename' => $filename,'sha256' => hash_file("sha256",$filepath)]);
+
+ $boundaryStr = "--{$boundary}\r\n";
+
+ $body = $boundaryStr;
+ $body .= 'Content-Disposition: form-data; name="meta"'."\r\n";
+ $body .= 'Content-Type: application/json'."\r\n";
+ $body .= "\r\n";
+ $body .= $options['sign_body']."\r\n";
+ $body .= $boundaryStr;
+ $body .= 'Content-Disposition: form-data; name="file"; filename="'.$filename.'"'."\r\n";
+ $body .= 'Content-Type: image/jpeg'.';'."\r\n";
+ $body .= "\r\n";
+ $body .= $file."\r\n";
+ $body .= "--{$boundary}--";
+
+ $options['data'] = (($body));
+
+ try {
+ $res = $this->request('/v3/merchant/media/upload', 'POST', $options, true);
+ }catch(\Exception $exception){
+ throw new ValidateException($exception->getMessage());
+ }
+
+ if(isset($res['code'])) throw new ValidateException('[微信接口返回]:' . $res['message']);
+
+ return $res;
+
+ }
+}
diff --git a/app/common/service/easywechat/merchant/ServiceProvider.php b/app/common/service/easywechat/merchant/ServiceProvider.php
new file mode 100644
index 0000000..fd32fcd
--- /dev/null
+++ b/app/common/service/easywechat/merchant/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace crmeb\services\easywechat\merchant;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author ClouderSky
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $pimple)
+ {
+ $pimple['sub_merchant'] = function ($pimple) {
+ return new Client($pimple['access_token'], $pimple);
+ };
+ }
+}
diff --git a/app/common/service/easywechat/storePay/Client.php b/app/common/service/easywechat/storePay/Client.php
new file mode 100644
index 0000000..be99b02
--- /dev/null
+++ b/app/common/service/easywechat/storePay/Client.php
@@ -0,0 +1,59 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace crmeb\services\easywechat\storePay;
+
+use crmeb\services\easywechat\BaseClient;
+use EasyWeChat\Core\AbstractAPI;
+use EasyWeChat\Core\AccessToken;
+use EasyWeChat\Core\Exceptions\HttpException;
+use EasyWeChat\Core\Http;
+use EasyWeChat\Payment\API;
+use EasyWeChat\Payment\Merchant;
+use GuzzleHttp\HandlerStack;
+use think\Exception;
+use EasyWeChat\Support\XML;
+use EasyWeChat\Support\Collection;
+use Psr\Http\Message\ResponseInterface;
+use think\exception\ValidateException;
+
+/**
+ * Class Client.
+ *
+ * @author ClouderSky
+ */
+class Client extends BaseClient
+{
+ const API = 'https://api.mch.weixin.qq.com';
+
+ public function transferBatches(array $data)
+ {
+ $api = '/v3/transfer/batches';
+ $params = [
+ "appid" => $this->app['config']['app_id'],
+ "out_batch_no" => "plfk2020042013",
+ "batch_name" => "分销明细",
+ "batch_remark" => "分销明细",
+ "total_amount" => 100,
+ "total_num" => 1,
+ "transfer_detail_list" => [
+ [
+ "openid" => "oOdvCvjvCG0FnCwcMdDD_xIODRO0",
+ "out_detail_no" => "x23zy545Bd5436",
+ "transfer_amount" => 100,
+ "transfer_remark" => "分销明细",
+ ]
+ ],
+ ];
+ $res = $this->request($api, 'POST', ['sign_body' => json_encode($params, JSON_UNESCAPED_UNICODE)], true);
+ }
+
+}
diff --git a/app/common/service/easywechat/storePay/ServiceProvider.php b/app/common/service/easywechat/storePay/ServiceProvider.php
new file mode 100644
index 0000000..938d8fd
--- /dev/null
+++ b/app/common/service/easywechat/storePay/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace crmeb\services\easywechat\storePay;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author ClouderSky
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $pimple)
+ {
+ $pimple['storePay'] = function ($pimple) {
+ return new Client($pimple['access_token'], $pimple);
+ };
+ }
+}
diff --git a/app/common/service/easywechat/subscribe/ProgramProvider.php b/app/common/service/easywechat/subscribe/ProgramProvider.php
new file mode 100644
index 0000000..57d0273
--- /dev/null
+++ b/app/common/service/easywechat/subscribe/ProgramProvider.php
@@ -0,0 +1,40 @@
+
+// +----------------------------------------------------------------------
+
+
+namespace crmeb\services\easywechat\subscribe;
+
+use EasyWeChat\MiniProgram\AccessToken;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * 注册订阅消息
+ * Class ProgramProvider
+ * @package crmeb\utils
+ */
+class ProgramProvider implements ServiceProviderInterface
+{
+ public function register(Container $pimple)
+ {
+ $pimple['mini_program.access_token'] = function ($pimple) {
+ return new AccessToken(
+ $pimple['config']['mini_program']['app_id'],
+ $pimple['config']['mini_program']['secret'],
+ $pimple['cache']
+ );
+ };
+
+ $pimple['mini_program.now_notice'] = function ($pimple) {
+ return new ProgramSubscribe($pimple['mini_program.access_token']);
+ };
+ }
+}
diff --git a/app/common/service/easywechat/subscribe/ProgramSubscribe.php b/app/common/service/easywechat/subscribe/ProgramSubscribe.php
new file mode 100644
index 0000000..993c66a
--- /dev/null
+++ b/app/common/service/easywechat/subscribe/ProgramSubscribe.php
@@ -0,0 +1,285 @@
+
+// +----------------------------------------------------------------------
+
+
+namespace crmeb\services\easywechat\subscribe;
+
+use EasyWeChat\Core\AbstractAPI;
+use EasyWeChat\Core\AccessToken;
+use EasyWeChat\Core\Exceptions\InvalidArgumentException;
+
+/**
+ * 小程序订阅消息
+ * Class ProgramSubscribe
+ * @package crmeb\utils
+ * @method $this
+ * @method $this template(string $template_id) 设置模板id
+ * @method $this withTemplateId(string $template_id) 设置模板id
+ * @method $this andTemplateId(string $template_id) 设置模板id
+ * @method $this andTemplate(string $template_id) 设置模板id
+ * @method $this andUses(string $template_id) 设置模板id
+ * @method $this to(string $touser) 设置opendid
+ * @method $this andReceiver(string $touser) 设置opendid
+ * @method $this withReceiver(string $touser) 设置opendid
+ * @method $this with(array $data) 设置发送内容
+ * @method $this andData(array $data) 设置发送内容
+ * @method $this withData(array $data) 设置发送内容
+ * @method $this data(array $data) 设置发送内容
+ * @method $this withUrl(string $page) 设置跳转路径
+ */
+class ProgramSubscribe extends AbstractAPI
+{
+
+ /**
+ * 添加模板接口
+ */
+ const API_SET_TEMPLATE_ADD = 'https://api.weixin.qq.com/wxaapi/newtmpl/addtemplate';
+
+ /**
+ * 删除模板消息接口
+ */
+ const API_SET_TEMPLATE_DEL = 'https://api.weixin.qq.com/wxaapi/newtmpl/deltemplate';
+
+ /**
+ * 获取模板消息列表
+ */
+ const API_GET_TEMPLATE_LIST = 'https://api.weixin.qq.com/wxaapi/newtmpl/gettemplate';
+
+ /**
+ * 获取模板消息分类
+ */
+ const API_GET_TEMPLATE_CATE = 'https://api.weixin.qq.com/wxaapi/newtmpl/getcategory';
+
+ /**
+ * 获取模板消息关键字
+ */
+ const API_GET_TEMPLATE_KEYWORKS = 'https://api.weixin.qq.com/wxaapi/newtmpl/getpubtemplatekeywords';
+
+ /**
+ * 获取公共模板
+ */
+ const API_GET_PUBLIC_TEMPLATE = 'https://api.weixin.qq.com/wxaapi/newtmpl/getpubtemplatetitles';
+
+ /**
+ * 发送模板消息
+ */
+ const API_SUBSCRIBE_SEND = 'https://api.weixin.qq.com/cgi-bin/message/subscribe/send';
+
+ /**
+ * Attributes
+ * @var array
+ */
+ protected $message = [
+ 'touser' => '',
+ 'template_id' => '',
+ 'page' => '',
+ 'data' => [],
+ ];
+
+ /**
+ * Message backup.
+ *
+ * @var array
+ */
+ protected $messageBackup;
+
+ protected $required = ['template_id', 'touser'];
+
+ /**
+ * ProgramSubscribeService constructor.
+ * @param AccessToken $accessToken
+ */
+ public function __construct(AccessToken $accessToken)
+ {
+ parent::__construct($accessToken);
+
+ $this->messageBackup = $this->message;
+
+ }
+
+ /**
+ * 获取当前拥有的模板列表
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ public function getTemplateList()
+ {
+ return $this->parseJSON('get', [self::API_GET_TEMPLATE_LIST]);
+ }
+
+ /**
+ * 获取公众模板列表
+ * @param string $ids
+ * @param int $start
+ * @param int $limit
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ public function getPublicTemplateList(string $ids, int $start = 0, int $limit = 10)
+ {
+ $params = [
+ 'ids' => $ids,
+ 'start' => $start,
+ 'limit' => $limit
+ ];
+ return $this->parseJSON('get', [self::API_GET_PUBLIC_TEMPLATE, $params]);
+ }
+
+ /**
+ * 获取模板分类
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ public function getTemplateCate()
+ {
+ return $this->parseJSON('get', [self::API_GET_TEMPLATE_CATE]);
+ }
+
+ /**
+ * 获取模板标题下的关键词列表
+ * @param string $tid 模板标题 id,可通过接口获取
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ public function getPublicTemplateKeywords(string $tid)
+ {
+ $params = [
+ 'tid' => $tid
+ ];
+ return $this->parseJSON('get', [self::API_GET_TEMPLATE_KEYWORKS, $params]);
+ }
+
+ /**
+ * 添加订阅模板消息
+ * @param string $tid 模板标题 id,可通过接口获取,也可登录小程序后台查看获取
+ * @param array $kidList 模板序列号 关键词顺序可以自由搭配(例如 [3,5,4] 或 [4,5,3]),最多支持5个,最少2个关键词组合
+ * @param string $sceneDesc 服务场景描述,15个字以内
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ public function addTemplate(string $tid, array $kidList, string $sceneDesc = '')
+ {
+ $params = [
+ 'tid' => $tid,
+ 'kidList' => $kidList,
+ 'sceneDesc' => $sceneDesc,
+ ];
+ return $this->parseJSON('json', [self::API_SET_TEMPLATE_ADD, $params]);
+ }
+
+ /**
+ * 删除模板消息
+ * @param string $priTmplId
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ public function delTemplate(string $priTmplId)
+ {
+ $params = [
+ 'priTmplId' => $priTmplId
+ ];
+ return $this->parseJSON('json', [self::API_SET_TEMPLATE_DEL, $params]);
+ }
+
+ /**
+ * 发送订阅消息
+ * @param array $data
+ * @return \EasyWeChat\Support\Collection|null
+ * @throws InvalidArgumentException
+ * @throws \EasyWeChat\Core\Exceptions\HttpException
+ */
+ public function send(array $data = [])
+ {
+ $params = array_merge($this->message, $data);
+
+ foreach ($params as $key => $value) {
+ if (in_array($key, $this->required, true) && empty($value) && empty($this->message[$key])) {
+ throw new InvalidArgumentException("Attribute '$key' can not be empty!");
+ }
+
+ $params[$key] = empty($value) ? $this->message[$key] : $value;
+ }
+
+ $params['data'] = $this->formatData($params['data']);
+
+ $this->message = $this->messageBackup;
+
+ return $this->parseJSON('json', [self::API_SUBSCRIBE_SEND, $params]);
+ }
+
+ /**
+ * 设置订阅消息发送data
+ * @param array $data
+ * @return array
+ */
+ protected function formatData(array $data)
+ {
+ $return = [];
+
+ foreach ($data as $key => $item) {
+ if (is_scalar($item)) {
+ $value = $item;
+ } elseif (is_array($item) && !empty($item)) {
+ if (isset($item['value'])) {
+ $value = strval($item['value']);
+ } elseif (count($item) < 2) {
+ $value = array_shift($item);
+ } else {
+ [$value] = $item;
+ }
+ } else {
+ $value = 'error data item.';
+ }
+
+ $return[$key] = ['value' => $value];
+ }
+
+ return $return;
+ }
+
+
+ /**
+ * Magic access..
+ *
+ * @param $method
+ * @param $args
+ * @return $this
+ */
+ public function __call($method, $args)
+ {
+ $map = [
+ 'template' => 'template_id',
+ 'templateId' => 'template_id',
+ 'uses' => 'template_id',
+ 'to' => 'touser',
+ 'receiver' => 'touser',
+ 'url' => 'page',
+ 'link' => 'page',
+ 'data' => 'data',
+ 'with' => 'data',
+ ];
+
+ if (0 === stripos($method, 'with') && strlen($method) > 4) {
+ $method = lcfirst(substr($method, 4));
+ }
+
+ if (0 === stripos($method, 'and')) {
+ $method = lcfirst(substr($method, 3));
+ }
+
+ if (isset($map[$method])) {
+ $this->message[$map[$method]] = array_shift($args);
+ }
+
+ return $this;
+ }
+
+}
diff --git a/app/common/service/merchant/WechatService.php b/app/common/service/merchant/WechatService.php
new file mode 100644
index 0000000..9da872c
--- /dev/null
+++ b/app/common/service/merchant/WechatService.php
@@ -0,0 +1,167 @@
+config = $config;
+ $this->application = new Application($config);
+ $this->application->register(new \crmeb\services\easywechat\certficates\ServiceProvider());
+ $this->application->register(new \crmeb\services\easywechat\merchant\ServiceProvider);
+ $this->application->register(new \crmeb\services\easywechat\combinePay\ServiceProvider);
+ $this->application->register(new \crmeb\services\easywechat\storePay\ServiceProvider);
+ }
+
+
+ /**
+ * @return self
+ * @author xaboy
+ * @day 2020-04-24
+ */
+ public static function create($isApp = null)
+ {
+ return new self(self::getConfig($isApp));
+ }
+
+ /**
+ * @return array
+ * @author xaboy
+ * @day 2020-04-24
+ */
+ public static function getConfig($isApp)
+ {
+ /** @var SystemConfigValue $make */
+ $make = app()->make(SystemConfigValue::class);
+ $wechat = $make->more([
+ 'wechat_appid', 'wechat_appsecret', 'wechat_token', 'wechat_encodingaeskey', 'wechat_encode', 'wecaht_app_appid', 'wechat_app_appsecret'
+ ], 0);
+
+ if ($isApp ?? request()->isApp()) {
+ $wechat['wechat_appid'] = trim($wechat['wecaht_app_appid']);
+ $wechat['wechat_appsecret'] = trim($wechat['wechat_app_appsecret']);
+ }
+ $payment = $make->more(['site_url', 'pay_weixin_mchid', 'pay_weixin_client_cert', 'pay_weixin_client_key', 'pay_weixin_key', 'pay_weixin_open',
+ 'wechat_service_merid', 'wechat_service_key', 'wechat_service_v3key', 'wechat_service_client_cert', 'wechat_service_client_key', 'wechat_service_serial_no'], 0);
+ $config = [
+ 'app_id' => trim($wechat['wechat_appid']),
+ 'secret' => trim($wechat['wechat_appsecret']),
+ 'token' => trim($wechat['wechat_token']),
+ 'routine_appId' => systemConfig('routine_appId'),
+ 'guzzle' => [
+ 'timeout' => 10.0, // 超时时间(秒)
+ 'verify' => false
+ ],
+ 'debug' => false,
+ ];
+ if ($wechat['wechat_encode'] > 0 && $wechat['wechat_encodingaeskey'])
+ $config['aes_key'] = trim($wechat['wechat_encodingaeskey']);
+ if (isset($payment['pay_weixin_open']) && $payment['pay_weixin_open'] == 1) {
+ $config['payment'] = [
+ 'merchant_id' => trim($payment['pay_weixin_mchid']),
+ 'key' => trim($payment['pay_weixin_key']),
+ 'cert_path' => (app()->getRootPath() . 'public' . $payment['pay_weixin_client_cert']),
+ 'key_path' => (app()->getRootPath() . 'public' . $payment['pay_weixin_client_key']),
+ 'notify_url' => $payment['site_url'] . Route::buildUrl('wechatNotify')->build(),
+ 'pay_weixin_client_cert' => $payment['pay_weixin_client_cert'],
+ 'pay_weixin_client_key' => $payment['pay_weixin_client_key'],
+ ];
+ }
+ $config['service_payment'] = [
+ 'merchant_id' => trim($payment['wechat_service_merid']),
+ 'type' => 'wechat',
+ 'key' => trim($payment['wechat_service_key']),
+ 'cert_path' => (app()->getRootPath() . 'public' . $payment['wechat_service_client_cert']),
+ 'key_path' => (app()->getRootPath() . 'public' . $payment['wechat_service_client_key']),
+ 'pay_weixin_client_cert' => $payment['wechat_service_client_cert'],
+ 'pay_weixin_client_key' => $payment['wechat_service_client_key'],
+ 'serial_no' => trim($payment['wechat_service_serial_no']),
+ 'apiv3_key' => trim($payment['wechat_service_v3key']),
+ ];
+ return $config;
+ }
+
+ /**
+ * @param $orderNo
+ * @param array $opt
+ * @author xaboy
+ * @day 2020-04-20
+ */
+ public function payOrderRefund($orderNo, array $opt)
+ {
+ if (!isset($opt['pay_price'])) throw new ValidateException('缺少pay_price');
+ $totalFee = floatval(bcmul($opt['pay_price'], 100, 0));
+ $refundFee = isset($opt['refund_price']) ? floatval(bcmul($opt['refund_price'], 100, 0)) : null;
+ $refundReason = isset($opt['desc']) ? $opt['desc'] : '';
+ $refundNo = isset($opt['refund_id']) ? $opt['refund_id'] : $orderNo;
+ $opUserId = isset($opt['op_user_id']) ? $opt['op_user_id'] : null;
+ $type = isset($opt['type']) ? $opt['type'] : 'out_trade_no';
+ /*仅针对老资金流商户使用
+ REFUND_SOURCE_UNSETTLED_FUNDS---未结算资金退款(默认使用未结算资金退款)
+ REFUND_SOURCE_RECHARGE_FUNDS---可用余额退款*/
+ $refundAccount = isset($opt['refund_account']) ? $opt['refund_account'] : 'REFUND_SOURCE_UNSETTLED_FUNDS';
+ try {
+ $res = ($this->refund($orderNo, $refundNo, $totalFee, $refundFee, $opUserId, $refundReason, $type, $refundAccount));
+ if ($res->return_code == 'FAIL') throw new ValidateException('退款失败:' . $res->return_msg);
+ if (isset($res->err_code)) throw new ValidateException('退款失败:' . $res->err_code_des);
+ } catch (Exception $e) {
+ throw new ValidateException($e->getMessage());
+ }
+ }
+
+ /**
+ * @param $orderNo
+ * @param $refundNo
+ * @param $totalFee
+ * @param null $refundFee
+ * @param null $opUserId
+ * @param string $refundReason
+ * @param string $type
+ * @param string $refundAccount
+ * @return Collection
+ */
+ public function refund($orderNo, $refundNo, $totalFee, $refundFee = null, $opUserId = null, $refundReason = '', $type = 'out_trade_no', $refundAccount = 'REFUND_SOURCE_UNSETTLED_FUNDS')
+ {
+ if (empty($this->config['payment']['pay_weixin_client_cert']) || empty($this->config['payment']['pay_weixin_client_key'])) {
+ throw new \Exception('请配置微信支付证书');
+ }
+ $totalFee = floatval($totalFee);
+ $refundFee = floatval($refundFee);
+ return $this->application->payment->refund($orderNo, $refundNo, $totalFee, $refundFee, $opUserId, $type, $refundAccount, $refundReason);
+ }
+
+}
\ No newline at end of file
diff --git a/composer.json b/composer.json
index d01d016..f59df86 100644
--- a/composer.json
+++ b/composer.json
@@ -31,7 +31,8 @@
"firebase/php-jwt": "6.1.2",
"symfony/var-exporter": "5.4.10",
"aliyuncs/oss-sdk-php": "^2.6",
- "overtrue/wechat": "~4.0"
+ "overtrue/wechat": "~5.0",
+ "topthink/think-queue": "^3.0"
},
"require-dev": {
"symfony/var-dumper": "^4.2",
diff --git a/config/queue.php b/config/queue.php
new file mode 100644
index 0000000..ac886b3
--- /dev/null
+++ b/config/queue.php
@@ -0,0 +1,38 @@
+
+// +----------------------------------------------------------------------
+
+return [
+ 'default' => 'redis',
+ 'connections' => [
+ 'sync' => [
+ 'type' => 'sync',
+ ],
+ 'database' => [
+ 'type' => 'database',
+ 'queue' => env('queue_name', 'default'),
+ 'table' => 'jobs',
+ ],
+ 'redis' => [
+ 'type' => 'redis',
+ 'queue' => env('queue_name', 'default'),
+ 'host' => env('redis.redis_hostname','127.0.0.1'),
+ 'port' => env('redis.port', '6379'),
+ 'password' => env('redis.redis_password', ''),
+ 'select' => (int)env('redis.select', 0),
+ 'timeout' => 0,
+ 'persistent' => false,
+ ],
+ ],
+ 'failed' => [
+ 'type' => 'none',
+ 'table' => 'failed_jobs',
+ ],
+];