diff --git a/.idea/.rakeTasks b/.idea/.rakeTasks new file mode 100644 index 0000000..107cb3a --- /dev/null +++ b/.idea/.rakeTasks @@ -0,0 +1,7 @@ + + diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..a26dc45 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..a9151ce --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/weixin_authorize.iml b/.idea/weixin_authorize.iml new file mode 100644 index 0000000..b938183 --- /dev/null +++ b/.idea/weixin_authorize.iml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..cbdadd3 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,1196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + + 1439821400232 + + + 1440091917475 + + + 1440092094009 + + + 1440092763011 + + + 1440093870370 + + + 1440093973508 + + + 1440094310821 + + + 1440095106887 + + + 1440095208908 + + + 1440096038566 + + + 1440096214636 + + + 1440096608977 + + + 1440096714769 + + + 1440096840561 + + + 1440097548108 + + + 1440097734472 + + + 1440097838964 + + + 1440098579202 + + + 1440098642876 + + + 1440099051051 + + + 1440099255490 + + + 1440099550113 + + + 1440100495920 + + + 1440100517009 + + + 1440100657703 + + + 1440100770873 + + + 1440101193646 + + + 1440102232406 + + + 1440105077689 + + + 1440106916059 + + + 1440171035338 + + + 1440268632880 + + + 1440269511969 + + + 1440269633036 + + + 1440270532103 + + + 1440271009484 + + + 1440271049335 + + + 1440316621413 + + + 1440317287288 + + + 1440317620647 + + + 1440317665614 + + + 1440317829018 + + + 1440317991694 + + + 1440318080300 + + + 1440318135553 + + + 1440318141276 + + + 1440318277498 + + + 1440318348063 + + + 1440318821585 + + + 1440437585987 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/weixin_authorize.rb b/lib/weixin_authorize.rb index 2800ebe..c996a53 100644 --- a/lib/weixin_authorize.rb +++ b/lib/weixin_authorize.rb @@ -28,6 +28,12 @@ module JsTicket autoload(:RedisStore, "weixin_authorize/js_ticket/redis_store") end + module ApiTicket + autoload(:Store, "weixin_authorize/api_ticket/store") + autoload(:ObjectStore, "weixin_authorize/api_ticket/object_store") + autoload(:RedisStore, "weixin_authorize/api_ticket/redis_store") + end + OK_MSG = "ok".freeze OK_CODE = 0.freeze GRANT_TYPE = "client_credential".freeze @@ -44,7 +50,7 @@ def http_get_without_token(url, url_params={}, endpoint="plain") def http_post_without_token(url, post_body={}, url_params={}, endpoint="plain") post_api_url = endpoint_url(endpoint, url) # to json if invoke "plain" - if endpoint == "plain" || endpoint == CUSTOM_ENDPOINT + if endpoint == "plain" || endpoint == "api" || endpoint == CUSTOM_ENDPOINT post_body = JSON.dump(post_body) end load_json(resource(post_api_url).post(post_body, params: url_params)) @@ -88,6 +94,17 @@ def open_endpoint(url) "https://open.weixin.qq.com#{url}" end + def check_required_options(options, names, module_name='Weixin Authorize') + missinglsit = [] + names.each do |name| + missinglsit << name if options.nil? || + !options.has_key?(name) || + options[name].nil? || + (!options[name].is_a?(Integer) && options[name].empty?) + end + raise "#{module_name}: missing required param(缺少参数): #{missinglsit.join(', ')}" + end + end -end +end \ No newline at end of file diff --git a/lib/weixin_authorize/api/card.rb b/lib/weixin_authorize/api/card.rb new file mode 100644 index 0000000..e893d85 --- /dev/null +++ b/lib/weixin_authorize/api/card.rb @@ -0,0 +1,799 @@ +# encoding: utf-8 +# =================== +# 传入参数必须为 Symbol +# =================== +module WeixinAuthorize + # 考虑了一下由于 Client 本身会引入所有的API + # 而 JsonHelper 不属于 API 部分 + # 所以还是把 JsonHelper 放在外层 + module CardJsonHelper + MODULE_JSON_HELPER_NAME = 'CardJsonHelper(微信卡券接口助手)' + class << self + INVOKE_SKU_REQUIRED_FIELDS = %i(quantity) + def sku(params) + params = { + quantity: nil + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_SKU_REQUIRED_FIELDS, MODULE_JSON_HELPER_NAME) + params + end + + INVOKE_DATEIINFO_REQUIRED_FIELDS = %i(type) + def date_info(params) + params = { + type: '', + begin_timestamp: nil, + end_timestamp: nil, + fixed_term: nil, + fixed_begin_term: nil + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_DATEIINFO_REQUIRED_FIELDS, MODULE_JSON_HELPER_NAME) + params + end + + INVOKE_BASEINFO_REQUIRED_FIELDS = %i(logo_url code_type brand_name title sub_title color notice description sku date_info) + # 创建卡券base_info json + def base_info(params) + params = { + # 必填参数 + logo_url: '', + code_type: '', + brand_name: '', + title: '', + sub_title: '', + color: '', + notice: '', + description: '', + sku: { quantity: nil }, + date_info: { + type: '', + begin_timestamp: nil, + end_timestamp: nil, + fixed_term: nil, + fixed_begin_term: nil + }, + # 可选参数 + use_custom_code: nil, + bind_openid: nil, + service_phone: nil, + location_id_list: nil, + source: nil, + custom_url_name: nil, + custom_url: nil, + custom_url_sub_title: nil, + promotion_url_name: nil, + promotion_url: nil, + promotion_url_sub_title: nil, + get_limit: nil, + can_share: nil, + can_give_friend: nil + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_BASEINFO_REQUIRED_FIELDS, MODULE_JSON_HELPER_NAME) + params + end + end + + class MemberCard + # 创建自定义会员信息类目json + # 会员卡激活后显示。 + INVOKE_CUSTOMCELL_REQUIRED_FIELDS = %i(name_type tips url) + def self.custom_cell(params) + params = { + name_type: '', + tips: '', + url: '' + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_CUSTOMCELL_REQUIRED_FIELDS, MODULE_JSON_HELPER_NAME) + params + end + + # 创建自定义会员信息类目json + # 会员信息类目名称,取以下任一值: + # name_type = + # 等级 FIELD_NAME_TYPE_LEVEL + # 印花 FIELD_NAME_TYPE_STAMP + # 折扣 FIELD_NAME_TYPE_DISCOUNT + # 成就 FIELD_NAME_TYPE_ACHIEVEMEN + # 里程 FIELD_NAME_TYPE_MILEAGE + # 优惠券 FIELD_NAME_TYPE_COUPON" + INVOKE_CUSTONFIELD_REQUIRED_FIELDS = %i(name_type url) + def self.custom_field(params) + params = { + name_type: '', + url: '' + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_CUSTONFIELD_REQUIRED_FIELDS, MODULE_JSON_HELPER_NAME) + params + end + + INVOKE_BONUS_REQUIRED_FIELDS = %i(supply_bonus) + def self.bonus(params) + params = { + supply_bonus: nil, + bonus_url: nil, + bonus_cleared: nil, + bonus_rules: nil + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_BONUS_REQUIRED_FIELDS, MODULE_JSON_HELPER_NAME) + params + end + + INVOKE_BALANCE_REQUIRED_FIELDS = %i(supply_balance) + def self.balance(params) + { + supply_balance: nil, + balance_url: nil, + balance_rules: nil + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_BALANCE_REQUIRED_FIELDS, MODULE_JSON_HELPER_NAME) + params + end + + INVOKE_MEMBERCARD_REQUIRED_FIELDS = %i(activate_url prerogative bonus balance) + # 创建会员卡json + def self.create(bash_info, params) + params = { + activate_url: '', + prerogative: '', + bonus: { + supply_bonus: nil, + bonus_url: nil, + bonus_cleared: nil, + bonus_rules: nil + }, + balance: { + supply_balance: nil, + balance_url: nil, + balance_rules: nil + }, + custom_field: [], + custom_cell: [] + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_MEMBERCARD_REQUIRED_FIELDS, MODULE_JSON_HELPER_NAME) + { + card: { + card_type: 'MEMBER_CARD', + base_info: bash_info, + activate_url: params[:activate_url], + prerogative: params[:prerogative], + supply_bonus: params[:bonus][:supply_bonus], + bonus_url: params[:bonus][:bonus_url], + bonus_cleared: params[:bonus][:bonus_cleared], + bonus_rules: params[:bonus][:bonus_rules], + supply_balance: params[:balance][:supply_balance], + balance_url: params[:balance][:balance_url], + balance_rules: params[:balance][:balance_rules], + custom_field1: params[:custom_field[0]], + custom_field2: params[:custom_field[1]], + custom_field3: params[:custom_field[2]], + custom_cell1: params[:custom_cell[0]] + } + } + end + end + + INVOKE_GROUPON_REQUIRED_FIELDS = %i(deal_detail) + class Groupon + # 创建团购券json + def self.create(bash_info, params) + params = { + deal_detail: '' + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_GROUPON_REQUIRED_FIELDS, MODULE_JSON_HELPER_NAME) + { + card: { + card_type: 'GROUPON', + base_info: bash_info, + deal_detail: params[:deal_detail] + } + } + end + end + + INVOKE_CASH_REQUIRED_FIELDS = %i(least_cost reduce_cost) + class Cash + # 创建代金券json + def self.create(bash_info, params) + params = { + least_cost: nil, + reduce_cost: nil + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_CASH_REQUIRED_FIELDS, MODULE_JSON_HELPER_NAME) + { + card: { + card_type: 'CASH', + base_info: bash_info, + least_cost: params[:least_cost], + reduce_cost: params[:reduce_cost], + } + } + end + end + + INVOKE_DISCOUNT_REQUIRED_FIELDS = %i(discount) + class Discount + # 创建折扣券json + def self.create(bash_info, params) + params = { + discount: nil + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_DISCOUNT_REQUIRED_FIELDS, MODULE_JSON_HELPER_NAME) + { + card: { + card_type: 'DISCOUNT', + base_info: bash_info, + discount: params[:discount] + } + } + end + end + + INVOKE_GIFT_REQUIRED_FIELDS = %i(gift) + class Gift + # 创建礼品券json + def self.create(bash_info, params) + params = { + gift: '' + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_GIFT_REQUIRED_FIELDS, MODULE_JSON_HELPER_NAME) + { + card: { + card_type: 'DISCOUNT', + base_info: bash_info, + gift: params[:gift] + } + } + end + end + + INVOKE_GENERALCOUPON_REQUIRED_FIELDS = %i(default_detail) + class GeneralCoupon + # 创建通用券json + def self.create(bash_info, params) + params = { + default_detail: '' + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_GENERALCOUPON_REQUIRED_FIELDS, MODULE_JSON_HELPER_NAME) + { + card: { + card_type: 'GENERAL_COUPON', + base_info: bash_info, + default_detail: params[:default_detail] + } + } + end + end + + INVOKE_MEETINGTICKET_REQUIRED_FIELDS = %i(meeting_detail) + class MeetingTicket + # 创建会议门票json + def self.create(bash_info, params) + params = { + meeting_detail: '', + map_url: nil + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_MEETINGTICKET_REQUIRED_FIELDS, MODULE_JSON_HELPER_NAME) + { + card: { + card_type: 'MEETING_TICKET', + base_info: bash_info, + meeting_detail: params[:meeting_detail], + map_url: params[:map_url] + } + } + end + end + + INVOKE_SCENICTICKET_REQUIRED_FIELDS = %i(ticket_class guide_url) + class ScenicTicket + # 创建景区门票json + def self.create(bash_info, params) + params = { + ticket_class: '', + guide_url: '' + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_SCENICTICKET_REQUIRED_FIELDS, MODULE_JSON_HELPER_NAME) + { + card: { + card_type: 'SCENIC_TICKET', + base_info: bash_info, + ticket_class: params[:ticket_class], + guide_url: params[:guide_url] + } + } + end + end + + INVOKE_BOARDPASS_REQUIRED_FIELDS = %i(from to flight air_model departure_time landing_time) + class BoardingPass + # 创建飞机票json + def self.create(bash_info, params) + params = { + from: '', + to: '', + flight: '', + gate: nil, + check_in_url: nil, + air_model: '', + departure_time: '', + landing_time: '' + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_BOARDPASS_REQUIRED_FIELDS, MODULE_JSON_HELPER_NAME) + { + card: { + card_type: 'BOARDING_PASS', + base_info: bash_info, + detail: params[:detail] + } + } + end + end + + INVOKE_MOVIETICKET_REQUIRED_FIELDS = %i(tickedetailt_class) + class MovieTicket + # 创建电影票json + def self.create(bash_info, params) + params = { + tickedetailt_class: '' + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_MOVIETICKET_REQUIRED_FIELDS, MODULE_JSON_HELPER_NAME) + { + card: { + card_type: 'MOVIE_TICKET', + base_info: bash_info, + detail: params[:detail] + } + } + end + end + end + + module Api + MODULE_API_CARD_NAME = 'Card API(微信卡券接口)' + module Card + def card_ext(card_id='', code=nil, openid=nil) + timestamp = Time.now.to_i + nonce_str = SecureRandom.hex(16) + apiticket = get_apiticket + sort_params = [ apiticket, timestamp.to_s, card_id.to_s, code.to_s, openid.to_s, nonce_str ].sort.join + signature = Digest::SHA1.hexdigest(sort_params) + { + code: code, + openid: openid, + timestamp: timestamp, + nonce_str: nonce_str, + signature: signature + } + end + + # 预览接口 + # https://api.weixin.qq.com/cgi-bin/message/mass/preview?access_token=ACCESS_TOKEN + # touser = option { openid: openid, wxname: wxname } + def card_send_preview(card_id='', towxname='', toopenid='') + url = "/message/mass/preview" + wxcard_body = { + card_id: card_id, + card_ext: card_ext + } + post_body = { + msgtype: 'wxcard', + touser: toopenid, + towxname: towxname + }.merge(wxcard_body) + http_post(url, post_body) + end + + # 获取卡券配色方案列表 + # https://api.weixin.qq.com/card/getcolors?access_token=TOKEN + def card_colors + url = "#{card_base_url}/getcolors" + http_get(url, {} ,'api') + end + + + # 统计卡券数据 - 拉取会员卡数据接口 + # https://api.weixin.qq.com/datacube/getcardmembercardinfo?access_token=ACCESS_TOKEN + def card_datacube_info_member_card(begin_date=0, end_date=0, cond_source=1) + begin_date = datacube_datetime_format begin_date + end_date = datacube_datetime_format end_date + url = "#{datacube_base_url}/getcardmembercardinfo" + post_body = { + begin_date: begin_date, + end_date: end_date, + cond_source: cond_source + } + http_post(url, post_body, {}, 'api') + end + + # 获取免费券数据接口 + # https://api.weixin.qq.com/datacube/getcardcardinfo?access_token=ACCESS_TOKEN + def card_datacube_info(begin_date=0, end_date=0, cond_source=1, card_id=nil) + begin_date = datacube_datetime_format begin_date + end_date = datacube_datetime_format end_date + url = "#{datacube_base_url}/getcardcardinfo" + post_body = { + begin_date: begin_date, + end_date: end_date, + cond_source: cond_source, + card_id: card_id + } + http_post(url, post_body, {}, 'api') + end + + # 拉取卡券概况数据接口 + # https://api.weixin.qq.com/datacube/getcardbizuininfo?access_token=ACCESS_TOKEN + def card_datacube(begin_date=0, end_date=0, cond_source=1) + begin_date = datacube_datetime_format begin_date + end_date = datacube_datetime_format end_date + url = "#{datacube_base_url}/getcardbizuininfo" + post_body = { + begin_date: begin_date, + end_date: end_date, + cond_source: cond_source + } + http_post(url, post_body, {}, 'api') + end + + # 设置测试白名单 + # https://api.weixin.qq.com/card/testwhitelist/set?access_token=TOKEN + def card_testwhitelist(wxusername=[], openid=[]) + url = "#{card_base_url}/testwhitelist/set" + post_body = { + openid: openid[0, 9], # 微信接口限制10个白名单用户,多余的抛弃 + username: wxusername[0,9] # 微信接口限制10个白名单用户,多余的抛弃 + } + http_post(url, post_body, {}, 'api') + end + + # 获取图文消息卡券HTML代码接口 + # https://api.weixin.qq.com/card/mpnews/gethtml?access_token=TOKEN + def card_mpnews_html(card_id=nil) + url = "#{card_base_url}/mpnews/gethtml" + post_body = { + card_id: card_id + } + http_post(url, post_body, {}, 'api') + end + + # 核查code接口 + # http://api.weixin.qq.com/card/code/checkcode?access_token=ACCESS_TOKEN + def card_code_check(card_id='', codelist=[]) + url = "#{card_base_url}/code/checkcode" + post_body = { + card_id: card_id, + code: codelist + } + http_post(url, post_body, {}, 'api') + end + + # 导入code接口 + # http://api.weixin.qq.com/card/code/deposit?access_token=ACCESS_TOKEN + # codelist = ['11111', '22222', '33333'] + def card_code_deposit(card_id='', codelist=[]) + url = "#{card_base_url}/code/deposit" + post_body = { + card_id: card_id, + code: codelist + } + http_post(url, post_body, {}, 'api') + end + + # 创建货架接口 + # https://api.weixin.qq.com/card/landingpage/create?access_token=$TOKEN + # + # 场景值 scene + # SCENE_NEAR_BY 附近 + # SCENE_SCAN 扫一扫 + # SCENE_SHAKE_TV 摇电视 + # SCENE_WIFI 微信wifi + # SCENE_IBEACON iBeacon + # SCENE_PAY 微信支付 + # SCENE_MENU 自定义菜单 + # SCENE_MONENTS_AD 朋友圈广告 + # SCENE_QRCODE 二维码 + # SCENE_ARTICLE 公众号文章 + # SCENE_H5 h5页面 + # SCENE_SHAKE_CARD 摇礼券 + # SCENE_BIZ_AD 公众号广告 + # SCENE_IVR 自动回复 + # SCENE_SMS 短信 + # SCENE_MEMBER_CARD_ANNOUNCEMENT 会员卡公告 + # SCENE_CARD_CUSTOM_CELL 卡券自定义cell + # SCENE_CARD_MSG_URL 卡券消息运营位 + # + def card_landingpage_create(banner='', title='', can_share=false, scene='', cardlist=[{cardid: '', thumb_url: ''}]) + url = "#{card_base_url}/landingpage/create" + post_body = { + banner: banner, + title: title, + can_share: can_share, + scene: scene, + cardlist: cardlist + } + http_post(url, post_body, {}, 'api') + end + + # 创建二维码接口 + # https://api.weixin.qq.com/card/qrcode/create?access_token=TOKEN + def card_qrcode_create(card_id='', code=nil, openid=nil, + expire_seconds=nil, is_unique_code=nil, outer_id=nil) + url = "#{card_base_url}/qrcode/create" + card_body = { + card_id: card_id, + code: code, + openid: openid, + is_unique_code: is_unique_code, + outer_id: outer_id + } + post_body = { + action_name: 'QR_CARD', + expire_seconds: expire_seconds, + action_info: {card: card_body} + } + http_post(url, post_body, {}, 'api') + end + + # Code解码接口 + # https://api.weixin.qq.com/card/code/decrypt?access_token=TOKEN + def card_code_decrypt(encrypt_code='') + url = "#{card_base_url}/code/decrypt" + post_body = { + encrypt_code: encrypt_code + } + http_post(url, post_body, {}, 'api') + end + + # 核销Code接口 + # https://api.weixin.qq.com/card/code/consume?access_token=TOKEN + def card_code_consume(card_id=nil, code='') + url = "#{card_base_url}/code/consume" + post_body = { + card_id: card_id, + code: code + } + http_post(url, post_body, {}, 'api') + end + + + # 设置卡券失效接口 + # https://api.weixin.qq.com/card/code/unavailable?access_token=TOKEN + def card_code_unavailable(card_id=nil, code='') + url = "#{card_base_url}/code/unavailable" + post_body = { + card_id: card_id, + code: code + } + http_post(url, post_body, {}, 'api') + end + + # 删除卡券接口 + # https://api.weixin.qq.com/card/delete?access_token=TOKEN + def card_delete(card_id='') + url = "#{card_base_url}/delete" + post_body = { + card_id: card_id + } + http_post(url, post_body, {}, 'api') + end + + + # 更改Code接口 + # https://api.weixin.qq.com/card/code/update?access_token=TOKEN + def card_code_update(card_id=nil, code='', new_code='') + url = "#{card_base_url}/code/update" + post_body = { + card_id: card_id, + code: code, + new_code: new_code + } + http_post(url, post_body, {}, 'api') + end + + # 修改库存接口 + # https://api.weixin.qq.com/card/modifystock?access_token=TOKEN + def card_modify_stock(card_id='', increase_stock_value=nil, reduce_stock_value=nil) + url = "#{card_base_url}/modifystock" + post_body = { + card_id: card_id, + increase_stock_value: increase_stock_value, + reduce_stock_value: reduce_stock_value + } + http_post(url, post_body, {}, 'api') + end + + # 更改卡券信息接口 + # https://api.weixin.qq.com/card/update?access_token=TOKEN + def card_update(card_id='', card_type='', card) + card = card[:card] if card.has_key?(:card) + post_body = { + card_id: card_id, + "#{card_type}": card + } + endpoint = 'api' + endpoint = 'plain' if card.is_a?(String) # 第三方开发者自定义json + url = "#{card_base_url}/update" + http_post(url, post_body, {}, endpoint) + end + + # 批量查询卡列表 + # https://api.weixin.qq.com/card/batchget?access_token=TOKEN + # offset => 查询卡列表的起始偏移量,从0开始,即offset: 5是指从从列表里的第六个开始读取。 + # count => 需要查询的卡片的数量(数量最大50)。 + # status_list => 支持开发者拉出指定状态的卡券列表,例:仅拉出通过审核的卡券。 + # + # 卡券状态 + # CARD_STATUS_NOT_VERIFY 待审核 + # CARD_STATUS_VERIFY_FALL 审核失败 + # CARD_STATUS_VERIFY_OK 通过审核 + # CARD_STATUS_USER_DELETE 卡券被用户删除 + # CARD_STATUS_USER_DISPATCH 在公众平台投放过的卡券 + def cards(offset=0, count=50, status_list=['CARD_STATUS_USER_DISPATCH']) + url = "#{card_base_url}/batchget" + post_body = { + offset: offset, + count: count, + status_list: status_list + } + http_post(url, post_body, {}, 'api') + end + + # 查看卡券详情 + # https://api.weixin.qq.com/card/get?access_token=TOKEN + def card(card_id='') + url = "#{card_base_url}/get" + post_body = { + card_id: card_id + } + http_post(url, post_body, {}, 'api') + end + + # 获取用户已领取卡券接口 + # https://api.weixin.qq.com/card/user/getcardlist?access_token=TOKEN + def user_cards(openid='', card_id=nil) + url = "#{card_base_url}/user/getcardlist" + post_body = { + openid: openid, + card_id: card_id + } + http_post(url, post_body, {}, 'api') + end + + + # 查询Code + # https://api.weixin.qq.com/card/code/get?access_token=TOKEN + def card_code(card_id=nil, code='') + url = "#{card_base_url}/code/get" + post_body = { + code: code, + card_id: card_id + } + http_post(url, post_body, {}, 'api') + end + + INVOKE_MEMBERCARD_ACTIVATE_REQUIRED_FIELDS = %i(membership_number code) + # 激活/绑定会员卡 + # https://api.weixin.qq.com/card/membercard/activate?access_token=TOKEN + def card_member_card_activate(params) + params = { + membership_number: '', + code: '', + activate_begin_time: nil, + activate_end_time: nil, + init_bonus: nil, + init_balance: nil, + init_custom_field_value1: nil, + init_custom_field_value2: nil, + init_custom_field_value3: nil, + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_MEMBERCARD_ACTIVATE_REQUIRED_FIELDS, MODULE_API_CARD_NAME) + url = "#{card_base_url}/membercard/activate" + http_post(url, params, {}, 'api') + end + + INVOKE_MEMBERCARD_UPDATE_REQUIRED_FIELDS = %i(code card_id) + # 更新会员信息 + # https://api.weixin.qq.com/card/membercard/updateuser?access_token=TOKEN + def card_member_card_update_user(params) + invoke_required_fields = INVOKE_MEMBERCARD_UPDATE_REQUIRED_FIELDS + invoke_required_fields << :bonus if params[:add_bonus].nil? + invoke_required_fields << :balance if params[:add_balance].nil? + params = { + code: '', + card_id: '', + add_bonus: nil, + bonus: nil, + record_bonus: '', + add_balance: nil, + balance: nil, + record_balance: '', + custom_field_value1: '', + custom_field_value2: '', + custom_field_value3: '' + }.merge(params) + WeixinAuthorize.check_required_options(params, invoke_required_fields, MODULE_API_CARD_NAME) + url = "#{card_base_url}/membercard/updateuser" + http_post(url, params, {}, 'api') + end + + INVOKE_MEETINGTICKET_UPDATE_REQUIRED_FIELDS = %i(code zone entrance seat_number) + # 更新会议门票 + # https://api.weixin.qq.com/card/meetingticket/updateuser?access_token=TOKEN + def card_meeting_ticket_update_user(params) + params = { + code: '', + card_id: nil, + begin_time: nil, + end_time: nil, + zone: '', + entrance: '', + seat_number: '' + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_MEETINGTICKET_UPDATE_REQUIRED_FIELDS, MODULE_API_CARD_NAME) + url = "#{card_base_url}/meetingticket/updateuser" + http_post(url, params, {}, 'api') + end + + INVOKE_MOVIETICKET_UPDATE_REQUIRED_FIELDS = %i(show_time duration code card_id ticket_class) + # 更新电影票 + # https://api.weixin.qq.com/card/movieticket/updateuser?access_token=TOKEN + def card_moive_ticket_update_user(params) + params = { + code: '', + card_id: '', + ticket_class: '', + screening_room: nil, + seat_number: nil, + show_time: nil, + duration: nil + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_MOVIETICKET_UPDATE_REQUIRED_FIELDS, MODULE_API_CARD_NAME) + url = "#{card_base_url}/movieticket/updateuser" + http_post(url, params, {}, 'api') + end + + INVOKE_BOARDPASS_UPDATE_REQUIRED_FIELDS = %i(code passenger_name etkt_bnr) + # 更新飞机票信息 + # https://api.weixin.qq.com/card/boardingpass/checkin?access_token=TOKEN + def card_boarding_pass_checkin(params) + params = { + code: '', + card_id: nil, + passenger_name: '', + class: '', + seat: nil, + etkt_bnr: nil, + qrcode_data: nil, + is_cancel: nil + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_BOARDPASS_UPDATE_REQUIRED_FIELDS, MODULE_API_CARD_NAME) + url = "#{card_base_url}/boardingpass/checkin" + http_post(url, params, {}, 'api') + end + + # 微信卡券创建接口 + # https://api.weixin.qq.com/card/create?access_token=ACCESS_TOKEN + def card_create(card) + endpoint = 'api' + endpoint = 'plain' if card.is_a?(String) # 第三方开发者自定义json + url = "#{card_base_url}/create" + http_post(url, card, {}, endpoint) + end + + private + def datacube_base_url + "/datacube" + end + + def card_base_url + "/card" + end + + def datacube_datetime_format(param) + param = Time.at(param) if param.is_a?(Integer) + param = param.strftime("%Y-%m-%d") if param.is_a?(Time) + param + end + + end + end +end diff --git a/lib/weixin_authorize/api/custom.rb b/lib/weixin_authorize/api/custom.rb index 9440c06..6ed91e4 100644 --- a/lib/weixin_authorize/api/custom.rb +++ b/lib/weixin_authorize/api/custom.rb @@ -145,6 +145,22 @@ def create_kf_session(account, open_id, text) http_post(KF_SESSION_URL, post_body, {}, CUSTOM_ENDPOINT) end + # 发送卡券 + # { + # "touser":"OPENID", + # "msgtype":"wxcard", + # "wxcard": + # { + # "card_id":"123dsdajkasd231jhksad", + # "card_ext": {code: code, openid: openid, timestamp: 1402057159, signature: '017bb17407c8e0058a66d72dcc61632b70f511ad'} + # } + # } + def send_card_custom(to_user='', card_id='') + wxcard = default_options(to_user, 'wxcard').merge( + {wxcard:{card_id: card_id, card_ext: card_ext(card_id)}}) + http_post(custom_base_url, wxcard) + end + private # https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN diff --git a/lib/weixin_authorize/api/mass.rb b/lib/weixin_authorize/api/mass.rb index 76d68ef..1d164fe 100644 --- a/lib/weixin_authorize/api/mass.rb +++ b/lib/weixin_authorize/api/mass.rb @@ -3,6 +3,12 @@ module WeixinAuthorize module Api module Mass + # 获取自动回复规则 + # https://api.weixin.qq.com/cgi-bin/get_current_autoreply_info?access_token=ACCESS_TOKEN + def mass_autoreply_rules + http_get('/get_current_autoreply_info') + end + MSG_TYPE = ["mpnews", "image", "text", "voice", "mpvideo"].freeze # media_info= {"media_id" media_id} diff --git a/lib/weixin_authorize/api/poi.rb b/lib/weixin_authorize/api/poi.rb new file mode 100644 index 0000000..54d55bc --- /dev/null +++ b/lib/weixin_authorize/api/poi.rb @@ -0,0 +1,105 @@ +# encoding: utf-8 +module WeixinAuthorize + module Api + module Poi + MODULE_NAME = 'POI API(微信门店接口)' + + INVOKE_POI_REQUIRED_FIELDS = %i(business_name branch_name + province city district address + telephone categories + offset_type longitude latitude + photo_list special open_time avg_price) + # 创建门店 + # http://api.weixin.qq.com/cgi-bin/poi/addpoi?access_token=TOKEN + def poi_add(params) + params = { + business_name: '', + branch_name: '', + province: '', + city: '', + district: '', + address: '', + telephone: '', + categories: '', + offset_type: 1, #火星坐标 + longitude: '', + latitude: '', + photo_list: [], + special: '', + open_time: '', + avg_price: nil, + sid: '', + introduction: nil, + recommend: nil, + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_POI_REQUIRED_FIELDS, MODULE_NAME) + post_body = { + business: { base_info: params } + } + url = "#{poi_base_url}/addpoi" + http_post(url, post_body) + end + + INVOKE_POI_UPDATE_REQUIRED_FIELDS = %i(poi_id) + # 修改门店 + # https://api.weixin.qq.com/cgi-bin/poi/updatepoi?access_token=TOKEN + def poi_update(params) + params = { + poi_id: '' + }.merge(params) + WeixinAuthorize.check_required_options(params, INVOKE_POI_UPDATE_REQUIRED_FIELDS, MODULE_NAME) + post_body = { + business: { base_info: params } + } + url = "#{poi_base_url}/updatepoi" + http_post(url, post_body) + end + + # 拉取门店类目表 + # http://api.weixin.qq.com/cgi-bin/api_getwxcategory?access_token=TOKEN + def poi_category() + http_get("/api_getwxcategory") + end + + + # 删除门店 + # https://api.weixin.qq.com/cgi-bin/poi/delpoi?access_token=TOKEN + def poi_delete(poi_id='') + url = "#{poi_base_url}/delpoi" + post_body = { + poi_id: poi_id + } + http_post(url, post_body) + end + + # 查询门店信息 + # http://api.weixin.qq.com/cgi-bin/poi/getpoi?access_token=TOKEN + def poi(poi_id='') + url = "#{poi_base_url}/getpoi" + post_body = { + poi_id: poi_id + } + http_post(url, post_body) + end + + + # 查询门店列表 + # https://api.weixin.qq.com/cgi-bin/poi/getpoilist?access_token=TOKEN + def pois(offset=0, limit=50) + url = "#{poi_base_url}/getpoilist" + post_body = { + begin: offset, + limit: limit + } + http_post(url, post_body) + end + + private + + def poi_base_url + "/poi" + end + + end + end +end diff --git a/lib/weixin_authorize/api_ticket/object_store.rb b/lib/weixin_authorize/api_ticket/object_store.rb new file mode 100644 index 0000000..78273d6 --- /dev/null +++ b/lib/weixin_authorize/api_ticket/object_store.rb @@ -0,0 +1,21 @@ +module WeixinAuthorize + module ApiTicket + class ObjectStore < Store + + def apiticket_expired? + # 如果当前token过期时间小于现在的时间,则重新获取一次 + client.apiticket_expired_at <= Time.now.to_i + end + + def apiticket + super + client.apiticket + end + + def refresh_apiticket + super + end + + end + end +end diff --git a/lib/weixin_authorize/api_ticket/redis_store.rb b/lib/weixin_authorize/api_ticket/redis_store.rb new file mode 100644 index 0000000..a972e63 --- /dev/null +++ b/lib/weixin_authorize/api_ticket/redis_store.rb @@ -0,0 +1,41 @@ +module WeixinAuthorize + module ApiTicket + class RedisStore < Store + APITICKET = "apiticket" + EXPIRED_AT = "expired_at" + + def apiticket_expired? + weixin_redis.hvals(client.apiticket_redis_key).empty? + end + + def refresh_apiticket + super + weixin_redis.hmset( + client.apiticket_redis_key, + APITICKET, + client.apiticket, + EXPIRED_AT, + client.apiticket_expired_at + ) + weixin_redis.expireat( + client.apiticket_redis_key, + client.apiticket_expired_at.to_i + ) + end + + def apiticket + super + client.apiticket = weixin_redis.hget(client.apiticket_redis_key, APITICKET) + client.apiticket_expired_at = weixin_redis.hget( + client.apiticket_redis_key, + EXPIRED_AT + ) + client.apiticket + end + + def weixin_redis + WeixinAuthorize.weixin_redis + end + end + end +end diff --git a/lib/weixin_authorize/api_ticket/store.rb b/lib/weixin_authorize/api_ticket/store.rb new file mode 100644 index 0000000..77aa16d --- /dev/null +++ b/lib/weixin_authorize/api_ticket/store.rb @@ -0,0 +1,40 @@ +# encoding: utf-8 +module WeixinAuthorize + module ApiTicket + class Store + + attr_accessor :client + + def initialize(client) + @client = client + end + + def self.init_with(client) + if WeixinAuthorize.weixin_redis.nil? + ObjectStore.new(client) + else + RedisStore.new(client) + end + end + + def apiticket_expired? + raise NotImplementedError, "Subclasses must implement a apiticket_expired? method" + end + + def refresh_apiticket + set_apiticket + end + + def apiticket + refresh_apiticket if apiticket_expired? + end + + def set_apiticket + result = client.http_get("/ticket/getticket", {type: 'wx_card'}).result + client.apiticket = result["ticket"] + client.apiticket_expired_at = result["expires_in"] + Time.now.to_i + end + + end + end +end diff --git a/lib/weixin_authorize/client.rb b/lib/weixin_authorize/client.rb index cfd9362..246c424 100644 --- a/lib/weixin_authorize/client.rb +++ b/lib/weixin_authorize/client.rb @@ -5,10 +5,15 @@ module WeixinAuthorize class Client +<<<<<<< HEAD + include Api::Poi +======= include MonitorMixin +>>>>>>> lanrion/master include Api::User + include Api::Card include Api::Menu include Api::Custom include Api::Groups @@ -21,14 +26,21 @@ class Client attr_accessor :app_id, :app_secret, :expired_at # Time.now + expires_in attr_accessor :access_token, :redis_key, :custom_access_token attr_accessor :jsticket, :jsticket_expired_at, :jsticket_redis_key + attr_accessor :apiticket, :apiticket_expired_at, :apiticket_redis_key # options: redis_key, custom_access_token def initialize(app_id, app_secret, options={}) @app_id = app_id @app_secret = app_secret @jsticket_expired_at = @expired_at = Time.now.to_i +<<<<<<< HEAD + @apiticket_expired_at = @expired_at = Time.now.to_i + @redis_key = security_redis_key(options[:redis_key] || "weixin_#{app_id}") +======= @redis_key = security_redis_key(options[:redis_key] || "weixin_#{app_id}") +>>>>>>> lanrion/master @jsticket_redis_key = security_redis_key("js_sdk_#{app_id}") + @apiticket_redis_key = security_redis_key("api_ticket_#{app_id}") @custom_access_token = options[:custom_access_token] super() # Monitor#initialize end @@ -57,6 +69,14 @@ def get_jsticket jsticket_store.jsticket end + def apiticket_store + ApiTicket::Store.init_with(self) + end + + def get_apiticket + apiticket_store.apiticket + end + # 获取js sdk 签名包 def get_jssign_package(url) timestamp = Time.now.to_i