domain(); $this->notifyUrl = $domainName . '/pay/notify/weChatCoupon'; // 平台id获取 $this->siteId = (int)$siteId; if($this->siteId <= 0) throw new Exception('缺少必要字段site_id!'); // 实例化请求数据 $this->app = $this->api(); } /** * Common: 请求配置 * Author: wu-hui * Time: 2023/06/28 14:20 * @return V3 * @throws \app\exception\ApiException */ private function api(){ // 普通模式 $payConfig = (new Config())->getPayConfig($this->siteId)['data']['value']; if($this->stortMode){ // 服务商模式 $subConfig = $payConfig; $payConfig = config('wechat.ispPay'); $payConfig['v3_pay_signkey'] = $payConfig['v3_signkey'] ?? ''; $payConfig['sub_mch_id'] = $subConfig['mch_id'] ?? ''; $payConfig['sub_pay_signkey'] = $subConfig['pay_signkey'] ?? ''; $payConfig['sub_v3_pay_signkey'] = $subConfig['v3_pay_signkey'] ?? ''; } $this->payConfig = $payConfig; if($payConfig) { $payConfig['site_id'] = $this->siteId; $this->subMchId = $payConfig['mch_id'] ?? ''; return (new V3($payConfig)); } } /** * Common: 发起请求 * Author: wu-hui * Time: 2023/06/28 14:40 * @param String $api * @param array $params * @param String $type * @return mixed * @throws Exception */ private function requestApi(String $api,array $params,String $type = 'post'){ $result = $this->app->requestApi($api,$params,$type); if($result['code'] == 0) return $result['data']; else throw new Exception($result['message']); } /** * Common: 商家券 —— 创建 * Author: wu-hui * Time: 2023/06/29 15:59 * @param $data * @return mixed * @throws Exception */ public function storeAdd($data){ // 发布参数获取 $params = $this->storeAddParams($data); // 发起请求 return $this->requestApi('v3/marketing/busifavor/stocks',$params); } /** * Common: 商家券 —— 创建参数获取 * Author: wu-hui * Time: 2023/06/29 15:57 * @param $data * @return array * @throws Exception */ private function storeAddParams($data){ $maxMoney = 10000000;// 金额限制 1千万(满减券-优惠金额、消费门槛;折扣券-消费门槛) // 商品可用描述 1=全部商品参与;2=指定商品参与 $goodsName = $data['goods_type'] == 1 ? '全部商品参与' : '指定商品参与'; // 批次类型 reward=满减;discount=折扣 $stockType = $data['type'] == 'reward' ? 'NORMAL' : 'DISCOUNT'; // 有效期时间信息获取 商家券有效期最长为1年 [$startTime,$endTime] = $this->storeAddParamsTime($data); $startTimeText = date("c",$startTime); $endTimeText = date("c",$endTime); // 获取配置信息 $weAppConfig = (new weAppConfig())->getWeappConfig($this->siteId)['data']['value']; $appid = $weAppConfig['appid']; $path = '/pages_market/goods/list'; // 判断:发放数量 & 领取数量 if((int)$data['count'] > 10000000 || (int)$data['count'] < 1) throw new Exception('发放数量应在1~1千万之间!'); if((int)$data['max_fetch'] > 100) throw new Exception('每个用户可领取数量应在1~100之间!'); // 凭据号 $outRequestNo = 'S'.date('YmdHi') . (string)rand(10000, 99999); // 配置请求参数 $params = [ 'stock_name' => $data['coupon_name'],// 批次名称,字数上限为21个,一个中文汉字/英文字母/数字均占用一个字数。 'belong_merchant' => $this->subMchId,// 批次归属商户号。 'goods_name' => $goodsName,// 用来描述批次在哪些商品可用,会显示在微信卡包中。字数上限为15个 'stock_type' => $stockType,// 批次类型:NORMAL=固定面额满减券;DISCOUNT=折扣券;EXCHANGE=换购券 // 核销规则 'coupon_use_rule' => [ // 券可核销时间 'coupon_available_time' => [ 'available_begin_time' => $startTimeText,// 开始时间;商家券有效期最长为1年 例如:2015-03-20T13:29:35+08:00 'available_end_time' => $endTimeText,// 结束时间;商家券有效期最长为1年 例如:2015-05-20T13:29:35+08:00 ], // 核销方式:OFF_LINE=线下滴码核销;MINI_PROGRAMS=线上小程序核销;PAYMENT_CODE=微信支付付款码核销;SELF_CONSUME=用户自助核销(暂不支持) 'use_method' => 'MINI_PROGRAMS', 'mini_programs_appid' => $appid,// 小程序appid;use_method=MINI_PROGRAMS 必填 'mini_programs_path' => $path,// 小程序path;use_method=MINI_PROGRAMS 必填 ], // 发放规则 'stock_send_rule' => [ 'max_coupons' => (int)$data['count'],// 批次最大可发放个数限制;特殊规则:取值范围 1 ≤ value ≤ 1000000000 'max_coupons_per_user' => (int)$data['max_fetch'],// 用户最大可领个数;用户可领个数,每个用户最多100张券 'natural_person_limit' => TRUE,// 【否】是否开启自然人限制;true=是,false=否(默认) 'prevent_api_abuse' => TRUE,// 【否】可疑账号拦截;true=是,false=否(默认) ], 'out_request_no' => $outRequestNo,// 商户创建批次凭据号(格式:商户id+日期+流水号),商户侧需保持唯一性 // 【否】自定义入口 卡详情页面,可选择多种入口引导用户。 'custom_entrance' => [ // 【否】小程序入口 'mini_programs_info' => [ 'mini_programs_appid' => $appid,// 商家小程序appid 'mini_programs_path' => $path,// 商家小程序path 'entrance_words' => '查看详情',// 入口文案;字数上限为5个,一个中文汉字/英文字母/数字均占用一个字数。 ], 'code_display_mode' => 'QRCODE',//【否】code展示模式:NOT_SHOW=不展示code;BARCODE=一维码;QRCODE=二维码 ], // 券code模式:WECHATPAY_MODE=系统分配,MERCHANT_API=商户发放时接口指定券code,MERCHANT_UPLOAD=商户上传自定义code,发券时系统随机选取上传的券code。 'coupon_code_mode' => 'WECHATPAY_MODE', // 【否】事件通知配置 事件回调通知商户的配置 'notify_config' => [ 'notify_appid' => $appid,// 【否】事件通知appid;小程序or公众号的APPID;不填则回调通知中涉及到用户身份信息的openid与unionid都将为空 ], ]; // 根据 【批次类型】 配置使用规则 $atLeast = (int)((float)$data['at_least'] * 100);// 消费门槛(单位分) if($atLeast > $maxMoney || $atLeast < 1) throw new Exception("消费门槛应在1元~1千万元之间"); if($stockType == 'NORMAL'){ // 固定面额满减券 $money = (int)((float)$data['money'] * 100);// 优惠金额(单位分) if($money > $maxMoney || $money < 1) throw new Exception("优惠金额应在1元~1千万元之间"); $params['coupon_use_rule']['fixed_normal_coupon'] = [ 'discount_amount' => $money,// 优惠金额,单位:分。取值范围 1 ≤ value ≤ 10000000 'transaction_minimum' => $atLeast,// 消费门槛,单位:分。特殊规则:取值范围 1 ≤ value ≤ 10000000 ]; }else{ // 折扣券 $discount = (float)((float)$data['discount'] * 10); if($discount <= 0 || $discount > 100) throw new Exception("折扣无效,折扣应在0.1~9.9之间!"); $params['coupon_use_rule']['discount_coupon'] = [ 'discount_percent' => $discount,// 折扣百分比,例如:86为八六折。 'transaction_minimum' => $atLeast,// 消费门槛,单位:分。特殊规则:取值范围 1 ≤ value ≤ 10000000 ]; } return $params; // ---- 全部参数 /*$params = [ 'stock_name' => '',// 批次名称,字数上限为21个,一个中文汉字/英文字母/数字均占用一个字数。 'belong_merchant' => '',// 批次归属于哪个商户。 'comment' => '',// 【否】仅配置商户可见,用于自定义信息。字数上限为20个, 'goods_name' => '',// 用来描述批次在哪些商品可用,会显示在微信卡包中。字数上限为15个 'stock_type' => '',// 批次类型:NORMAL=固定面额满减券;DISCOUNT=折扣券;EXCHANGE=换购券 // 核销规则 'coupon_use_rule' => [ // 券可核销时间 'coupon_available_time' => [ 'available_begin_time' => '',// 开始时间,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE 商家券有效期最长为1年 例如:2015-03-20T13:29:35+08:00 'available_end_time' => '',// 结束时间,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE 商家券有效期最长为1年 例如:2015-05-20T13:29:35+08:00 'available_day_after_receive' => '',// 【否】生效后N天内有效,1=当天,2=明天;以此类推。 'wait_days_after_receive' => '',// 领取后N天开始生效;领取后立即生效则不填写,第二天则填1,以此类推;最大不能超过30天 // 【否】固定周期时间段 'available_week' => [ 'week_day' => '',//0代表周日,1代表周一,以此类推;当填写available_day_time时,week_day必填 // 当天可用时间段 可以填写多个时间段,最多不超过2个。 'available_day_time' => [ [ 'begin_time' => '',// 当天可用开始时间 当天可用开始时间,单位:秒,1代表当天0点0分1秒。 'end_time' => '',// 当天可用结束时间 当天可用结束时间,单位:秒,86399代表当天23点59分59秒。 ] ], ], // 【否】无规律的有效时间段 多个无规律时间段,用户自定义字段。 'irregulary_avaliable_time' => [ [ 'begin_time' => '',// 开始时间:格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE 例:2015-05-20T13:29:35+08:00 'end_time' => '',// 结束时间:格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE 例:2015-05-20T13:29:35+08:00 ] ], ], // 【否】固定面额满减券使用规则 stock_type为NORMAL时必填。 'fixed_normal_coupon' => [ 'discount_amount' => '',// 优惠金额,单位:分。取值范围 1 ≤ value ≤ 10000000 'transaction_minimum' => '',// 消费门槛,单位:分。特殊规则:取值范围 1 ≤ value ≤ 10000000 ], // 【否】折扣券使用规则 stock_type为DISCOUNT时必填。 'discount_coupon' => [ 'discount_percent' => '',// 折扣百分比,例如:86为八六折。 'transaction_minimum'=> '',// 消费门槛,单位:分。特殊规则:取值范围 1 ≤ value ≤ 10000000 ], // 【否】换购券使用规则 stock_type为EXCHANGE时必填 'exchange_coupon' => [ 'exchange_price' => '',//单品换购价,单位:分。特殊规则:取值范围 0 ≤ value ≤ 10000000 'transaction_minimum' => '',// 消费门槛,单位:分。特殊规则:取值范围 0 ≤ value ≤ 10000000 ], // 核销方式:OFF_LINE=线下滴码核销;MINI_PROGRAMS=线上小程序核销;PAYMENT_CODE=微信支付付款码核销;SELF_CONSUME=用户自助核销(暂不支持) 'use_method' => 'MINI_PROGRAMS', 'mini_programs_appid' => '',// 小程序appid;use_method=MINI_PROGRAMS 必填 'mini_programs_path' => '',// 小程序path;use_method=MINI_PROGRAMS 必填 ], // 发放规则 'stock_send_rule' => [ 'max_coupons' => '',// 批次最大可发放个数限制;特殊规则:取值范围 1 ≤ value ≤ 1000000000 'max_coupons_per_user' => '',// 用户最大可领个数;用户可领个数,每个用户最多100张券 'max_coupons_by_day' => '',// 【否】单天发放上限个数;stock_type=DISCOUNT或者stock_type=EXCHANGE;取值范围 1 ≤ value ≤ 1000000000 'natural_person_limit' => '',// 【否】是否开启自然人限制;true=是,false=否(默认) 'prevent_api_abuse' => '',// 【否】可疑账号拦截;true=是,false=否(默认) 'transferable' => '',// 【否、未开放】是否允许转赠;true=是,false=否(默认) 'shareable' => '',// 【否、未开放】是否允许分享链接;true=是,false=否(默认) ], 'out_request_no' => '',// 商户创建批次凭据号(格式:商户id+日期+流水号),商户侧需保持唯一性 // 【否】自定义入口 卡详情页面,可选择多种入口引导用户。 'custom_entrance' => [ // 【否】小程序入口 'mini_programs_info' => [ 'mini_programs_appid' => '',// 商家小程序appid 'mini_programs_path' => '',// 商家小程序path 'entrance_words' => '',// 入口文案;字数上限为5个,一个中文汉字/英文字母/数字均占用一个字数。 'guiding_words' => '',// 【否】引导文案;字数上限为6个,一个中文汉字/英文字母/数字均占用一个字数。 ], 'appid' => '',// 【否】商户公众号appid;可配置商户公众号,从券详情可跳转至公众号,用户自定义字段。 'hall_id' => '',// 【否】营销馆id; 'store_id' => '',// 【否】可用门店id 'code_display_mode' => 'QRCODE',//【否】code展示模式:NOT_SHOW=不展示code;BARCODE=一维码;QRCODE=二维码 ], // 【否】样式信息 创建批次时的样式信息 'display_pattern_info' => [ 'description' => '',// 【否】使用须知;用于说明详细的活动规则,会展示在代金券详情页。 'merchant_logo_url' => '',// 【否,建议上传】商户logo;大小需为120像素*120像素;支持JPG/JPEG/PNG格式,且图片小于1M 'merchant_name' => '',//【否】商户名称;展示上限12个字符。不支持商户自定义 'background_color' => '',//【否】背景颜色;使用微信指定颜色值,不能自定义 'coupon_image_url' => '',//【否】券详情图片;1074像素(宽)*603像素(高),图片大小不超过2M,支持JPG/PNG格式 ], // 【否】视频号相关信息 'finder_info' => [ 'finder_id' => '',// 视频号ID 'finder_video_id' => '',// 视频号视频ID 'finder_video_cover_image_url' => '',// 视频号封面图;图片尺寸:1074像素(宽)*603像素(高),图片大小不超过2M,支持JPG/PNG格式。 ], 'coupon_code_mode' => '',// 券code模式:WECHATPAY_MODE=系统分配,MERCHANT_API=商户发放时接口指定券code,MERCHANT_UPLOAD=商户上传自定义code,发券时系统随机选取上传的券code。 // 【否】事件通知配置 事件回调通知商户的配置 'notify_config' => [ 'notify_appid' => '',// 【否】事件通知appid;小程序or公众号的APPID;不填则回调通知中涉及到用户身份信息的openid与unionid都将为空 ], 'subsidy' => '',// 【否】是否允许营销补贴;true=允许,false=否(默认) ];*/ } /** * Common: 商家券 —— 创建参数 - 有效期时间参数获取 * Author: wu-hui * Time: 2023/06/29 15:06 * @param $data * @return array */ private function storeAddParamsTime($data):array{ // 0=固定时间,1=领取之日起,2=长期有效 商家券有效期最长为1年 $startTime = (int)(time() + $this->offsetTime); switch((int)$data['validity_type']){ case 0: // 固定时间 $maxEndTime = strtotime(date('Y-m-d H:i:s', $startTime)." +1 year"); $endTime = (int)$data['end_time'] > $maxEndTime ? $maxEndTime : (int)$data['end_time']; break; default: $endTime = strtotime(date('Y-m-d H:i:s', $startTime)." +1 year"); } return [$startTime,$endTime]; } /** * Common: 商家券 —— 查看详情 * Author: wu-hui * Time: 2023/06/29 16:15 * @param $stockId * @return mixed * @throws Exception */ public function storeDetail($stockId){ // 请求参数配置 $params = []; // 发起请求 return $this->requestApi("v3/marketing/busifavor/stocks/$stockId",$params,'get'); } /** * Common: 商家券 —— 核销用户券 * Author: wu-hui * Time: 2023/07/10 16:50 * @param $stockId * @param $couponCode * @param $useRequestNo * @param string $openId * @return mixed * @throws Exception */ public function storeMemberUse($stockId,$couponCode,$useRequestNo,$openId = ''){ // 获取配置信息 $weAppConfig = (new weAppConfig())->getWeappConfig($this->siteId)['data']['value']; // 发起请求 $params = [ 'coupon_code' => $couponCode,// 券的唯一标识。 'stock_id' => $stockId,//微信为每个商家券批次分配的唯一ID 'appid' => $weAppConfig['appid'],// 支持小程序appid与公众号appid 'use_time' => date("c",time()),// 商户请求核销用户券的时间;例如:2015-05-20T13:29:35+08:00 'use_request_no' => $useRequestNo,// 每次核销请求的唯一标识,商户需保证唯一。 'openid' => $openId,// 【否】用户的唯一标识,做安全校验使用 ]; return $this->requestApi("v3/marketing/busifavor/coupons/use",$params); } /** * Common: 商家券 —— 根据过滤条件查询用户券 * Author: wu-hui * Time: 2023/07/10 14:47 * @param $openid * @param int $offset * @param string $status * @param int $stockId * @return mixed * @throws Exception */ public function storeMemberSelect($openid,int $offset = 0,string $status = '',int $stockId = 0){ // 获取配置信息 $weAppConfig = (new weAppConfig())->getWeappConfig($this->siteId)['data']['value']; // 请求参数配置 $params = [ 'appid' => $weAppConfig['appid'],// 支持小程序appid与公众号appid // 'stock_id' => $stockId,// 【否】微信为每个商家券批次分配的唯一ID,是否指定批次号查询 // 'coupon_state' => '',// 【否】券状态:SENDED=可用,USED=已核销,EXPIRED=已过期 'creator_merchant' => $this->subMchId,// 【否】批次创建方商户号,当调用方商户号(即请求头中的商户号)为创建批次方商户号时,该参数必传 // 'belong_merchant' => '',// 【否】批次归属商户号,当调用方商户号(即请求头中的商户号)为批次归属商户号时,该参数必传 // 'sender_merchant' => '',// 【否】批次发放商户号,当调用方商户号(即请求头中的商户号)为批次发放商户号时,该参数必传;委托营销关系下,请填写委托发券的商户号 'offset' => $offset,// 分页页码 'limit' => 20,// 分页大小 ]; if($stockId > 0) $params['stock_id'] = $stockId; if(!empty($status)) $params['coupon_state'] = $status; // 发起请求 return $this->requestApi("v3/marketing/busifavor/users/$openid/coupons",$params,'get'); } /** * Common: 商家券 —— 查询用户单张券详情 * Author: wu-hui * Time: 2023/07/10 15:05 * @param $openid * @param string $couponCode * @return mixed * @throws Exception */ public function storeMemberSingleDetail($openid,string $couponCode){ // 获取配置信息 $weAppConfig = (new weAppConfig())->getWeappConfig($this->siteId)['data']['value']; $appid = $weAppConfig['appid'];// 支持小程序appid与公众号appid // 发起请求 $params = []; return $this->requestApi("v3/marketing/busifavor/users/$openid/coupons/$couponCode/appids/$appid",$params,'get'); } /** * Common: 商家券 —— 设置商家券事件通知地址 * Author: wu-hui * Time: 2023/07/10 15:08 * @return mixed * @throws Exception */ public function storeNotifySet(){ // 请求参数配置 $params = [ 'mchid' => $this->subMchId,// 微信支付商户的商户号,由微信支付生成并下发,不填默认查询调用方商户的通知URL。 'notify_url' => $this->notifyUrl,// 商户提供的用于接收商家券事件通知的url地址,必须支持https。 ]; // 发起请求 return $this->requestApi("v3/marketing/busifavor/callbacks",$params); } /** * Common: 商家券 —— 查询商家券事件通知地址 * Author: wu-hui * Time: 2023/07/10 15:30 * @return mixed * @throws Exception */ public function storeNotifySee(){ // 请求参数配置 $params = [ 'mchid' => $this->subMchId,// 微信支付商户的商户号,由微信支付生成并下发,不填默认查询调用方商户的通知URL ]; // 发起请求 return $this->requestApi("v3/marketing/busifavor/callbacks",$params,'get'); } /** * Common: 商家券 —— 关联订单信息 * Author: wu-hui * Time: 2023/07/10 15:43 * @param $stockId * @param $couponCode * @param $outTradeNo * @return mixed * @throws Exception */ public function storeOrderJoin($stockId,$couponCode,$outTradeNo){ $outRequestNo = 'COJ'.date('YmdHi') . (string)rand(10000, 99999); // 请求参数配置 $params = [ 'stock_id' => $stockId,// 微信为每个商家券批次分配的唯一ID,对于商户自定义code的批次,关联请求必须填写批次号 'coupon_code' => $couponCode,// 券的唯一标识 'out_trade_no' => $outTradeNo,// 微信支付下单时的商户订单号,欲与该商家券关联的微信支付 'out_request_no' => $outRequestNo,// 商户创建批次凭据号(格式:商户id+日期+流水号),商户侧需保持唯一性,可包含英文字母,数字,|,_,*,-等内容,不允许出现其他不合法符号。 ]; // 发起请求 $result = $this->requestApi("v3/marketing/busifavor/coupons/associate",$params); $result['out_request_no'] = $outRequestNo; return $result; } /** * Common: 商家券 —— 取消关联订单信息 * Author: wu-hui * Time: 2023/07/10 16:05 * @param $stockId * @param $couponCode * @param $outTradeNo * @param $outRequestNo * @return mixed * @throws Exception */ public function storeOrderJoinCancel($stockId,$couponCode,$outTradeNo,$outRequestNo){ // 请求参数配置 $params = [ 'stock_id' => $stockId,// 微信为每个商家券批次分配的唯一ID,对于商户自定义code的批次,关联请求必须填写批次号 'coupon_code' => $couponCode,// 券的唯一标识 'out_trade_no' => $outTradeNo,// 微信支付下单时的商户订单号,欲与该商家券关联的微信支付 'out_request_no' => $outRequestNo,// 商户创建批次凭据号(格式:商户id+日期+流水号),商户侧需保持唯一性,可包含英文字母,数字,|,_,*,-等内容,不允许出现其他不合法符号。 ]; // 发起请求 return $this->requestApi("v3/marketing/busifavor/coupons/disassociate",$params); } /** * Common: 商家券 —— 修改批次预算 * Author: wu-hui * Time: 2023/07/10 17:29 * @param $stockId * @param $num * @param $oldNum * @param string $type * @return mixed * @throws Exception */ public function storeUpdateBudget($stockId,$num,$oldNum,$type = 'max'){ $modifyBudgetRequestNo = 'UB'.date('YmdHi') . (string)rand(10000, 99999); // 请求参数配置 $params = [ 'modify_budget_request_no' => $modifyBudgetRequestNo,// 修改预算请求单据号 ]; // 根据修改类型进行处理 type:max=修改批次最大发放个数,day=修改单天发放上限个数 if($type == 'max'){ $params['target_max_coupons'] = $num;// 批次最大发放个数 二选一 $params['current_max_coupons'] = $oldNum;// 当前批次最大发放个数,当传入target_max_coupons大于0时,current_max_coupons必传 } else{ $params['target_max_coupons_by_day'] = $num;// 单天发放上限个数 二选一 $params['current_max_coupons_by_day'] = $oldNum;// 当前单天发放上限个数 ,当传入target_max_coupons_by_day大于0时,current_max_coupons_by_day必填 } // 发起请求 return $this->requestApi("v3/marketing/busifavor/stocks/$stockId/budget",$params,'patch'); } /** * Common: 商家券 —— 修改商家券基本信息(TODO:未对接 - 目前商家券没有需要修改的内容所以暂不对接修改接口) * Author: wu-hui * Time: 2023/07/11 9:57 * @param $data * @param $id * @return mixed * @throws Exception */ public function storeUpdateBaseInfo($data,$id){ $stockId = model('promotion_coupon_type')->getValue([ ['coupon_type_id','=',$id] ],'weapp_stock_id'); // 发布参数获取 $params = $this->storeUpdateParams($data); // 发起请求 return $this->requestApi("v3/marketing/busifavor/stocks/$stockId",$params,'patch'); } /** * Common: 商家券 —— 编辑参数获取(TODO:未对接 - 目前商家券没有需要修改的内容所以暂不对接修改接口) * Author: wu-hui * Time: 2023/07/11 9:57 * @param $data * @return array */ public function storeUpdateParams($data){ return [ // 【否】自定义入口 卡详情页面,可选择多种入口引导用户。 'custom_entrance' => [ // 【否】小程序入口 'mini_programs_info' => [ 'mini_programs_appid' => '',// 商家小程序appid 'mini_programs_path' => '',// 商家小程序path 'entrance_words' => '查看详情',// 入口文案;字数上限为5个,一个中文汉字/英文字母/数字均占用一个字数。 'guiding_words' => '', // 小程序入口引导文案,字数上限为6个,一个中文汉字/英文字母/数字均占用一个字数。 ], 'appid' => '',// 【否】可配置商户公众号,从券详情可跳转至公众号 'hall_id' => '',// 【否】填写微信支付营销馆的馆id,用户自定义字段。 营销馆需在商户平台 创建 'code_display_mode' => 'QRCODE',//【否】code展示模式:NOT_SHOW=不展示code;BARCODE=一维码;QRCODE=二维码 ], 'comment' => '',// 【否】批次备注,仅配置商户可见,用于自定义信息。字数上限为20个 'goods_name' => '',// 【否】用来描述批次在哪些商品可用,会显示在微信卡包中。字数上限为15个,一个中文汉字/英文字母/数字均占用一个字数。 'out_request_no' => '',// 商户修改批次凭据号(格式:商户id+日期+流水号),商户侧需保持唯一性。 // 【否】样式信息 创建批次时的样式信息 'display_pattern_info' => [ 'description' => '',// 【否】使用须知;用于说明详细的活动规则,会展示在代金券详情页。 'background_color' => '',//【否】背景颜色;使用微信指定颜色值,不能自定义 'coupon_image_url' => '',//【否】券详情图片;1074像素(宽)*603像素(高),图片大小不超过2M,支持JPG/PNG格式 // 【否】视频号相关信息 'finder_info' => [ 'finder_id' => '',// 视频号ID 'finder_video_id' => '',// 视频号视频ID 'finder_video_cover_image_url' => '',// 视频号封面图;图片尺寸:1074像素(宽)*603像素(高),图片大小不超过2M,支持JPG/PNG格式。 ], ], // 【否】核销规则 'coupon_use_rule' => [ // 核销方式:OFF_LINE=线下滴码核销;MINI_PROGRAMS=线上小程序核销;PAYMENT_CODE=微信支付付款码核销;SELF_CONSUME=用户自助核销(暂不支持) 'use_method' => 'MINI_PROGRAMS', 'mini_programs_appid' => '',// 小程序appid;use_method=MINI_PROGRAMS 必填 'mini_programs_path' => '',// 小程序path;use_method=MINI_PROGRAMS 必填 ], // 【否】发放规则 'stock_send_rule' => [ 'prevent_api_abuse' => '',// 【否】可疑账号拦截;true=是,false=否(默认) ], // 【否】事件通知配置 事件回调通知商户的配置 'notify_config' => [ 'notify_appid' => '',// 【否】事件通知appid;小程序or公众号的APPID;不填则回调通知中涉及到用户身份信息的openid与unionid都将为空 ], ]; } /** * Common: 商家券 —— 申请退券 * Author: wu-hui * Time: 2023/07/11 9:14 * @param $couponCode * @param $stockId * @return mixed * @throws Exception */ public function storeRefund($couponCode,$stockId){ $returnRequestNo = 'CR'.date('YmdHi') . (string)rand(10000, 99999); // 请求参数配置 $params = [ 'coupon_code' => $couponCode,// 券的唯一标识 'stock_id' => $stockId,// 券的所属批次号 'return_request_no' => $returnRequestNo,// 每次退券请求的唯一标识,商户需保证唯一 ]; // 发起请求 $result = $this->requestApi("v3/marketing/busifavor/coupons/return",$params); $result['return_request_no'] = $returnRequestNo; return $result; } /** * Common: 商家券 —— 使券失效 * Author: wu-hui * Time: 2023/07/11 9:23 * @param $couponCode * @param $stockId * @param string $reason * @return mixed * @throws Exception */ public function storeDeactivate($couponCode,$stockId,$reason = ''){ $returnRequestNo = 'CD'.date('YmdHi') . (string)rand(10000, 99999); // 请求参数配置 $params = [ 'coupon_code' => $couponCode,// 券的唯一标识 'stock_id' => $stockId,// 券的所属批次号 'deactivate_request_no' => $returnRequestNo,// 每次失效请求的唯一标识,商户需保证唯一 ]; if(trim($reason)) $params['deactivate_reason'] = $reason;//失效的原因 // 发起请求 $result = $this->requestApi("v3/marketing/busifavor/coupons/deactivate",$params); $result['deactivate_request_no'] = $returnRequestNo; return $result; } /** * Common: 代金券 —— 发布 * Author: wu-hui * Time: 2023/06/28 14:32 * @param $data * @return mixed * @throws Exception * @throws \app\exception\ApiException */ public function voucherAdd($data){ // 发布参数获取 $params = $this->voucherParams($data); // 发起请求 return $this->requestApi('v3/marketing/favor/coupon-stocks',$params); } /** * Common: 代金券 —— 发布 - 请求参数获取 * Author: wu-hui * Time: 2023/06/27 14:01 * @param $data * @return array * @throws Exception */ private function voucherParams($data):array{ // 时间信息获取 [$startTime,$endTime] = $this->voucherParamsTime($data); $startTimeText = date("c",$startTime); $endTimeText = date("c",$endTime); // 优惠劵发放数量 $count = (int)$data['count'];// 设置发放优惠劵数量 $maxCount = 10000000; if($count < 5 || $count > $maxCount) throw new Exception('优惠劵发放数量必须大于5且小于等于1000万!'); // 优惠劵面额和最高预算(单位:分) 面额-必须为整数、必须大于1元且小于等于500元 if((float)$data['money'] < 1 || (float)$data['money'] > 500) throw new Exception("优惠劵面额必须大于1元且小于等于500元!"); $money = (int)((float)$data['money'] * 100);// 金额(单位分) $maxMoney = (int)$money * $count;// 最高预算金额(单位分) // 最大领取数量 $maxFetch = (int)$data['max_fetch']; if($maxFetch < 1 || $maxFetch > 60 || $maxFetch > $count) throw new Exception('最大领取数量必须大于1且小于等于60且不能超过发放数量!'); // 使用条件 $atLeast = (float)($data['at_least'] * 100); if($atLeast <= $money) throw new Exception('使用条件金额必须大于优惠劵面额!'); // 凭据号 $outRequestNo = 'V'.date('YmdHi') . (string)rand(10000, 99999); // 信息配置 $paramsConfig = [ 'stock_name' => $data['coupon_name'],// * 批次名称,批次名称最多9个中文汉字、最多20个字母、不能包含不当内容和特殊字符 _ , ; | 'belong_merchant' => $this->subMchId,// * 归属商户号 'available_begin_time' => $startTimeText,// * 批次开始时间;开始时间不可早于当前时间、不能创建365天后开始的批次、批次可用时间范围最长为90天 例如:2015-05-20T13:29:35+08:00 'available_end_time' => $endTimeText,// * 批次结束时间;结束时间需晚于开始时间、可用时间最长为90天、有效时间间隔最短为1s 例如:2015-05-20T13:29:35+08:00 // 批次使用规则 'stock_use_rule' => [ 'max_coupons' => $count,// * 最大发券数;发放总个数最少5个、发放总个数最多1000万个 'max_amount' => $maxMoney,// * 最大发券预算;批次总预算最多1000万元 单位:分 max_amount需要等于coupon_amount(面额) * max_coupons(发放总上限) 'max_coupons_per_user' => $maxFetch,// * 活动期间每个用户可领个数;不能大于发放总个数、最少为1个,最多为60个 'natural_person_limit' => true,// * 是否开启自然人限制;多个微信号同属于一个身份证时,视为同一用户,枚举值:true 是、false 否 'prevent_api_abuse' => true,// * 是否开启防刷拦截;当用户命中恶意、小号、机器、羊毛党、黑产等风险行为时,无法成功发放代金券 是=true、否=false ], // 代金券详情页 'pattern_info' => [ 'description' => '微信支付营销代金券',// * 使用说明;最多1000个UTF8字符 ], // 核销规则 'coupon_use_rule' => [ // 固定面额满减券使用规则 stock_type为NORMAL时必填。 'fixed_normal_coupon' => [ 'coupon_amount' => $money,// 面额,单位:分;必须为整数、必须大于1元且小于等于500元 'transaction_minimum' => $atLeast,// 使用券金额门槛,单位:分;使用门槛必须大于优惠金额 ], 'available_merchants' => [$this->subMchId],// 可用商户的交易才可核销/使用代金券 ], 'no_cash' => true,// 营销经费。枚举值:true:免充值、false:预充值 'stock_type' => 'NORMAL',// 批次类型,仅支持:NORMAL:固定面额满减券批次 'out_request_no' => $outRequestNo,// 商户创建批次凭据号 ]; if($data['goods_ids']) $paramsConfig['coupon_use_rule']['available_items'] = explode(',',$data['goods_ids']); return $paramsConfig; // ---- 全部参数 // $params = [ // 'stock_name' => '',// * 批次名称,批次名称最多9个中文汉字、最多20个字母、不能包含不当内容和特殊字符 _ , ; | // // 'comment' => '',// 批次备注,批次备注最多60个UTF8字符数 // 'belong_merchant' => '',// * 归属商户号 // 'available_begin_time' => '',// * 批次开始时间;开始时间不可早于当前时间、不能创建365天后开始的批次、批次可用时间范围最长为90天 // 'available_end_time' => '',// * 批次结束时间;结束时间需晚于开始时间、可用时间最长为90天、有效时间间隔最短为1s // // 批次使用规则 // 'stock_use_rule' => [ // 'max_coupons' => 0, // * 最大发券数;发放总个数最少5个、发放总个数最多1000万个 // 'max_amount' => '', // * 最大发券预算;批次总预算最多1000万元 单位:分 max_amount需要等于coupon_amount(面额) * max_coupons(发放总上限) // // 'max_amount_by_day' => 0, // 单天最大发券预算;不能大于总预算、max_amount_by_day不可以为0 // 'max_coupons_per_user' => '',// * 活动期间每个用户可领个数;不能大于发放总个数、最少为1个,最多为60个 // 'natural_person_limit' => true,// *是否开启自然人限制;多个微信号同属于一个身份证时,视为同一用户,枚举值:true 是、false 否 // 'prevent_api_abuse' => true,// *是否开启防刷拦截;当用户命中恶意、小号、机器、羊毛党、黑产等风险行为时,无法成功发放代金券 是=true、否=false // ], // // 代金券详情页 // 'pattern_info' => [ // 'description' => '',// * 使用说明;最多1000个UTF8字符 // // 'merchant_logo' => '',// 商户logo;商户logo ,仅支持通过《图片上传API》接口获取的图片URL地址、商户logo大小需为120像素*120像素、支持JPG/JPEG/PNG格式,且图片小于1M、最多128个UTF8字符 // // 'merchant_name' => '',// 品牌名称;最多12个中文汉字、最多36个英文字符 // // 'background_color' => '',// 背景颜色;色值需要参考卡券背景颜色图中的颜色值标识、不能直接使用RGB或者16位颜色值 // // 'coupon_image' => '',// 券详情图片;850像素*350像素,且图片大小不超过2M,支持JPG/PNG格式,仅支持通过《图片上传API》接口获取的图片URL地址。 // ], // // 核销规则 // 'coupon_use_rule' => [ // // 允许指定券的特殊生效时间规则 - 该字段暂未开放 // /*'coupon_available_time' => [ // // 固定时间段可用 // 'fix_available_time' => [ // 'available_week_day' => '',// 允许指定每周固定星期数生效,0代表周日生效,1代表周一生效,以此类推;不填则代表在可用时间内周一至周日都生效。 // 'begin_time'=> '',// 当天开始时间 // 'end_time' => '',// 当天结束时间 // ], // 'second_day_available' => '',// 领取后N天有效;领取后,券的开始时间为领券后第二天,如7月1日领券,那么在7月2日00:00:00开始;true=是,false=否 // 'available_time_after_receive' => '',// 领取后,券的结束时间为领取N天后;单位:分钟 // ],*/ // // 固定面额满减券使用规则 stock_type为NORMAL时必填。 // 'fixed_normal_coupon' => [ // 'coupon_amount' => '',// 面额,单位:分;必须为整数、必须大于1元且小于等于500元 // 'transaction_minimum' => '',// 使用券金额门槛,单位:分;使用门槛必须大于优惠金额 // ], // // 'goods_tag' => '',// 订单优惠标记,按json格式;最多允许录入50个、每个订单优惠标记支持字母/数字/下划线,不超过128个UTF8字符 // // 'limit_pay' => '',// 指定付款方式;可指定零钱付款、指定银行卡付款,需填入支付方式编码、例如:[CFT,ICBC_CREDIT] // // 指定银行卡bin付款的交易可核销/使用代金券,当批次限定了指定银行卡时方可生效 // /*'limit_card' => [ // 'name' => '',// 银行卡名称 // 'bin' => '',// 指定卡BIN;使用指定卡BIN的银行卡支付方可享受优惠,按json格式 例如:['62123456','62123457'] // ],*/ // // 'trade_type' => [],// 允许指定支付方式的交易才可核销/使用,不填则默认不限:MICROAPP:小程序支付、APPPAY:APP支付、PPAY:免密支付、CARD:刷卡支付、FACE:人脸支付、OTHER:其他支付 // // 'combine_use' => '',// 允许指定本优惠是否可以和本商户号创建的其他券同时使用,不填则默认允许同时使用。枚举值:true=是、false=否 // // 'available_items'=> '',// 可核销商品编码;单个商品编码的字符长度为【1,128】、条目个数限制为【1,50】 // // 'unavailable_items' => '',// 不可核销的商品编码或条目编码;单个商品编码的字符长度为【1,128】、条目个数限制为【1,50】 // 'available_merchants'=> '',// 可用商户的交易才可核销/使用代金券 // ], // 'no_cash' => '',// 营销经费。枚举值:true:免充值、false:预充值 // 'stock_type' => 'NORMAL',// 批次类型,仅支持:NORMAL:固定面额满减券批次 // 'out_request_no' => '',// 商户创建批次凭据号 // // '商户创建批次凭据号' => [],// 扩展属性字段,按json格式,如无需要则不填写。 // ]; } /** * Common: 代金券 —— 发布 - 请求参数获取(时间参数处理) * Author: wu-hui * Time: 2023/06/27 11:16 * @param $data * @return array * @throws Exception */ private function voucherParamsTime($data):array{ switch((int)$data['validity_type']){ case 0: // 固定时间 $startTime = (int)(time() + $this->offsetTime); $maxEndTime = strtotime(date('Y-m-d H:i:s', $startTime)." +90 day"); $endTime = (int)$data['end_time']; // 判断:过期时间不能超出90天 if($endTime > $maxEndTime) throw new Exception('有效期错误,微信代金券过期时间不能超过90天!'); break; case 1: // 领取之日起 throw new Exception('有效期类型错误,微信代金券禁止使用该类型!'); break; case 2: // 长期有效 throw new Exception('有效期类型错误,微信代金券禁止使用该类型!'); break; default: throw new Exception('有效期类型错误,微信代金券禁止使用该类型!'); } return [$startTime,$endTime]; } /** * Common: 代金券 —— 激活代金券批次 * Author: wu-hui * Time: 2023/06/28 14:49 * @param $stockId * @return mixed * @throws Exception */ public function voucherActivation($stockId){ // 参数获取 $params = [ 'stock_creator_mchid' => $this->subMchId,// 批次创建方商户号 ]; // 发起请求 return $this->requestApi("v3/marketing/favor/stocks/$stockId/start",$params); } /** * Common: 代金券 —— 发放代金券批次 * Author: wu-hui * Time: 2023/06/29 10:21 * @param $stockId * @param $openid * @return mixed * @throws Exception */ public function voucherGrant($stockId,$openid){ // 生成凭据号 $outRequestNo = $this->subMchId.'NO'. (string)rand(10000, 99999); // 获取配置信息 $weAppConfig = (new weAppConfig())->getWeappConfig($this->siteId)['data']['value']; // 参数获取 $params = [ 'stock_id' => json_encode($stockId),// 批次id 'out_request_no' => $outRequestNo,// 商户此次发放凭据号 'appid' => $weAppConfig['appid'],// 小程序appid || 公众号appid || APP的appid 'stock_creator_mchid' => $this->subMchId,// 批次创建方商户号 ]; // 发起请求 return $this->requestApi("v3/marketing/favor/users/$openid/coupons",$params); } /** * Common: 代金券 —— 暂停代金券批次 * Author: wu-hui * Time: 2023/06/29 9:05 * @param $stockId * @return mixed * @throws Exception */ public function voucherSuspend($stockId){ // 参数获取 $params = [ 'stock_creator_mchid' => $this->subMchId,// 批次创建方商户号 ]; // 发起请求 return $this->requestApi("v3/marketing/favor/stocks/$stockId/pause",$params); } /** * Common: 代金券 —— 重启代金券批次 * Author: wu-hui * Time: 2023/06/29 9:06 * @param $stockId * @return mixed * @throws Exception */ public function voucherRestart($stockId){ // 参数获取 $params = [ 'stock_creator_mchid' => $this->subMchId,// 批次创建方商户号 ]; // 发起请求 return $this->requestApi("v3/marketing/favor/stocks/$stockId/restart",$params); } /** * Common: 代金券 —— 条件查询批次列表 * Author: wu-hui * Time: 2023/06/28 16:14 * @param int $offset * @return mixed * @throws Exception */ public function voucherBatchList(int $offset = 0){ // 参数获取 $params = [ 'offset' => $offset,// 页码从0开始,默认第0页 'limit' => 10,// 分页大小,最大10 'stock_creator_mchid' => $this->subMchId,// 创建批次的商户号 // 'create_start_time' => '',// 起始创建时间 格式:yyyy-MM-DDTHH:mm:ss+TIMEZONE 请求协议url编码 // 'create_end_time' => '',// 终止创建时间 格式:yyyy-MM-DDTHH:mm:ss+TIMEZONE 请求协议url编码 // 'status' => '',// 批次状态:unactivated=未激活;audit=审核中;running=运行中;stoped=已停止;paused:暂停发放 ]; // 发起请求 return $this->requestApi("v3/marketing/favor/stocks",$params,'get'); } /** * Common: 代金券 —— 查询批次详情 * Author: wu-hui * Time: 2023/06/29 9:10 * @param $stockId * @return mixed * @throws Exception */ public function voucherBatchDetail($stockId){ // 参数获取 $params = [ 'stock_creator_mchid' => $this->subMchId,// 批次创建方商户号 ]; // 发起请求 return $this->requestApi("v3/marketing/favor/stocks/$stockId",$params,'get'); } /** * Common: 代金券 —— 查询代金券详情 * Author: wu-hui * Time: 2023/06/29 10:37 * @param $openid * @param $couponId * @return mixed * @throws Exception */ public function voucherDetail($openid,$couponId){ // 获取配置信息 $weAppConfig = (new weAppConfig())->getWeappConfig($this->siteId)['data']['value']; // 参数获取 $params = [ 'appid' => $weAppConfig['appid'],// 小程序appid || 公众号appid || APP的appid ]; // 发起请求 return $this->requestApi("v3/marketing/favor/users/$openid/coupons/$couponId",$params,'get'); } /** * Common: 代金券 —— 查询代金券可用商户 * Author: wu-hui * Time: 2023/06/29 9:15 * @param $offset * @param $stockId * @return mixed * @throws Exception */ public function voucherAvailableMerchants($offset,$stockId){ // 参数获取 $params = [ 'offset' => $offset,// 页码从0开始,默认第0页 'limit' => 10,// 分页大小,最大10 'stock_creator_mchid' => $this->subMchId,// 创建批次的商户号 ]; // 发起请求 return $this->requestApi("v3/marketing/favor/stocks/$stockId/merchants",$params,'get'); } /** * Common: 代金券 —— 查询代金券可用单品 * Author: wu-hui * Time: 2023/06/29 9:17 * @param $offset * @param $stockId * @return mixed * @throws Exception */ public function voucherAvailableGoods($offset,$stockId){ // 参数获取 $params = [ 'offset' => $offset,// 页码从0开始,默认第0页 'limit' => 10,// 分页大小,最大10 'stock_creator_mchid' => $this->subMchId,// 创建批次的商户号 ]; // 发起请求 return $this->requestApi("v3/marketing/favor/stocks/$stockId/items ",$params,'get'); } /** * Common: 代金券 —— 根据商户号查用户的券 * Author: wu-hui * Time: 2023/06/29 10:33 * @param $offset * @param $stockId * @param $openid * @return mixed * @throws Exception */ public function voucherMemberCoupon($offset,$stockId,$openid){ // 获取配置信息 $weAppConfig = (new weAppConfig())->getWeappConfig($this->siteId)['data']['value']; // 参数获取 $params = [ 'appid' => $weAppConfig['appid'],// 公众账号ID 'stock_id' => $stockId,// 批次id // 'status' => 'SENDED',//SENDED=返回可用,USED=返回可用+已实扣 'creator_mchid' => $this->subMchId,// 批次创建方商户号 'offset' => $offset,// 页码从0开始,默认第0页 // 'limit' => 10,// 分页大小,默认20 ]; // 发起请求 return $this->requestApi("v3/marketing/favor/users/$openid/coupons",$params,'get'); } /** * Common: 代金券 —— 下载批次核销明细 * Author: wu-hui * Time: 2023/06/29 10:42 * @param $stockId * @return mixed * @throws Exception */ public function voucherDownloadBatchUseDetails($stockId){ // 参数获取 $params = []; // 发起请求 return $this->requestApi("v3/marketing/favor/stocks/$stockId/use-flow",$params,'get'); } /** * Common: 代金券 —— 下载批次退款明细 * Author: wu-hui * Time: 2023/06/29 10:48 * @param $stockId * @return mixed * @throws Exception */ public function voucherDownloadBatchRefundDetails($stockId){ // 参数获取 $params = []; // 发起请求 return $this->requestApi("v3/marketing/favor/stocks/$stockId/refund-flow",$params,'get'); } /** * Common: 代金券 —— 设置消息通知地址;可接收营销相关的事件通知:核销通知 * Author: wu-hui * Time: 2023/06/29 11:23 * @return mixed * @throws Exception */ public function voucherSetNotify(){ // 参数获取 $params = [ 'mchid' => $this->subMchId, 'notify_url' => $this->notifyUrl, // 'switch' => true,// true=开启推送;false=停止推送。 false暂不支持使用,如果想关闭通知请在商户平台关闭券的回调通知 ]; // 发起请求 return $this->requestApi("v3/marketing/favor/callbacks",$params); } /** * Common: 获取微信优惠券领取信息 * Author: wu-hui * Time: 2023/07/07 15:17 * @param $info * @return array */ public function getWeAppConfig($info){ // 判断:如果没有配置信息 则返回空数组 if(!$this->payConfig) return []; // 优惠券信息 每条信息代表领取一张卡券 一次最多可以领取10张 $outRequestNo = 'WEAPP'.date('YmdHi') . (string)rand(10000, 99999); $sendCouponParams = [ [ 'stock_id' => $info['weapp_stock_id'] ?? 0,//微信支付券批次id 'out_request_no' => $outRequestNo,//发券凭证,可包含英文字母,数字,|,_,*,-等内容,不允许出现其他不合法符号,需在单个批次单个用户下确保唯一性 // 'create_coupon_merchant' => $this->subMchId,//创建支付券的商户号;如果是支付券,则是必填项;商家券无需填写 'customize_send_time' => date("c",time()),// 商家券在商户业务系统里的实际领取时间,目前只支持商家券、支付券暂不支持。示例值:2015-05-20T13:29:35+08:00 ] ]; $key = $this->payConfig['pay_signkey']; // 生成签名 $signArr = []; foreach($sendCouponParams as $index => &$sendCouponParamsItem){ // 判断:如果是代金券 需要添加【create_coupon_merchant】字段内容;如果是商家券则不需要该字段 if($info['weapp_channel'] == 'voucher') $sendCouponParamsItem['create_coupon_merchant'] = $this->subMchId; // 循环处理 获取加密数组 foreach($sendCouponParamsItem as $sendCouponParamsKey => $subSendCouponParamsItem){ $signArr[$sendCouponParamsKey.$index] = $subSendCouponParamsItem; } } $signArr['send_coupon_merchant'] = $this->subMchId; ksort($signArr); $signStr = urldecode(http_build_query($signArr))."&key=".$key; $sign = strtoupper(hash_hmac("sha256", $signStr, $key)); return [ 'send_coupon_params' => $sendCouponParams, 'sign' => $sign,//签名计算值 签名方式:HMAC-SHA256。 'send_coupon_merchant' => $this->subMchId,//发券商户号 ]; } }