six_001
This commit is contained in:
@@ -0,0 +1,302 @@
|
||||
from typing import Literal, Optional
|
||||
|
||||
import emoji
|
||||
import orjson
|
||||
import pydash
|
||||
import requests
|
||||
from django.forms import model_to_dict
|
||||
from pydantic import BaseModel, Field
|
||||
from ulid import ULID
|
||||
|
||||
from django_celery.message.tasks import mini调用微信小程序交易管理服务录入订单接口
|
||||
from wechat_token.token工具包 import sixToken
|
||||
from wechat_数据库.支付订单数据库.商品订单数据 import order订单
|
||||
from wechat_项目工具包.小程序交易管理服务.小程序交易订单管理工具 import miniTransaction交易管理服务工具
|
||||
|
||||
|
||||
class ems_parameters物流的状态(BaseModel):
|
||||
demo_必填: int = Field(..., description="描述") # 必传属发生
|
||||
demo_可选: Optional[int] = None # 可选值
|
||||
demo_默认值: int = Field(default=100001, description="描述") # 有默认值
|
||||
demo_联合类型: Literal["xiao", "zizi"] = Field(..., description="描述")
|
||||
揽件成功: str = Field("揽件成功", description="描述")
|
||||
揽件失败: str = Field("揽件失败", description="描述")
|
||||
分配业务员: str = Field("分配业务员", description="分配业务员")
|
||||
更新运输轨迹: str = Field("更新运输轨迹", description="更新运输轨迹")
|
||||
开始派送: str = Field("开始派送", description="开始派送")
|
||||
签收成功: str = Field("签收成功", description="签收成功")
|
||||
签收失败: str = Field("签收失败", description="签收失败")
|
||||
订单取消: str = Field("订单取消", description="订单取消")
|
||||
订单滞留: str = Field("订单滞留", description="订单滞留")
|
||||
|
||||
|
||||
class ems邮政物流工具(BaseModel):
|
||||
# ems_biz_id: str = "1100244903172"
|
||||
# ems_delivery_id: str = "EMS"
|
||||
ems_password: str = "xiao@792282"
|
||||
ems_biz_id: str = Field("test_biz_id", description="ems_biz_id")
|
||||
ems_delivery_id: str = Field("TEST", description="ems_biz_id")
|
||||
|
||||
def ems发件人信息(self):
|
||||
ems发件人信息数据 = {
|
||||
"name": "六果泡泡", # 否 发/收件人姓名,不超过64字节
|
||||
"tel": "18860891975", # string 否 发/收件人座机号码,若不填写则必须填写 mobile,不超过32字节
|
||||
"mobile": "18860891970", # string 否 发/收件人手机号码,若不填写则必须填写 tel,不超过32字节
|
||||
"company": "江苏六果科技有限公司", # string 否 发/收件人公司名称,不超过64字节
|
||||
"post_code": "225300", # string 否 发/收件人邮编,不超过10字节
|
||||
"country": "中国", # string 否 发/收件人国家,不超过64字节
|
||||
"province": "江苏省", # string 否 发/收件人省份,比如:"广东省",不超过64字节
|
||||
"city": "泰州市", # string 否 发/收件人市/地区,比如:"广州市",不超过64字节
|
||||
"area": "海陵区", # string 否 发/收件人区/县,比如:"海珠区",不超过64字节
|
||||
"address": "", # string 否 发/收件人详细地址,比如:"XX路XX号XX大厦XX",不超过512 字节
|
||||
}
|
||||
return ems发件人信息数据
|
||||
|
||||
def ems收件人信息(self, order物流商家侧订单号):
|
||||
订单对象 = order订单.objects.get(order物流订单号=order物流商家侧订单号)
|
||||
orderData = model_to_dict(instance=订单对象, fields=None)
|
||||
provinceName = pydash.get(obj=orderData, path="order用户配送地址.address.provinceName")
|
||||
cityName = pydash.get(obj=orderData, path="order用户配送地址.address.cityName") # 市
|
||||
countyName = pydash.get(obj=orderData, path="order用户配送地址.address.countyName") # 区
|
||||
detailInfoNew = pydash.get(obj=orderData, path="order用户配送地址.address.detailInfoNew") # 详细地址
|
||||
telNumber = pydash.get(obj=orderData, path="order用户配送地址.address.telNumber") # 详细地址
|
||||
detailInfo = pydash.get(obj=orderData, path="order用户配送地址.address.detailInfo") # 详细地址
|
||||
userName = pydash.get(obj=orderData, path="order用户配送地址.address.userName") # 详细地址
|
||||
nationalCode = pydash.get(obj=orderData, path="order用户配送地址.address.nationalCode") # 详细地址
|
||||
print("\n🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 \n", telNumber)
|
||||
return {
|
||||
"name": userName, # 否 发/收件人姓名,不超过64字节
|
||||
"tel": telNumber, # string 否 发/收件人座机号码,若不填写则必须填写 mobile,不超过32字节
|
||||
"mobile": telNumber, # string 否 发/收件人手机号码,若不填写则必须填写 tel,不超过32字节
|
||||
"company": "", # string 否 发/收件人公司名称,不超过64字节
|
||||
"post_code": nationalCode, # string 否 发/收件人邮编,不超过10字节
|
||||
"country": "中国", # string 否 发/收件人国家,不超过64字节
|
||||
"province": provinceName, # string 否 发/收件人省份,比如:"广东省",不超过64字节
|
||||
"city": cityName, # string 否 发/收件人市/地区,比如:"广州市",不超过64字节
|
||||
"area": countyName, # string 否 发/收件人区/县,比如:"海珠区",不超过64字节
|
||||
"address": detailInfoNew, # string 否 发/收件人详细地址,比如:"XX路XX号XX大厦XX",不超过512 字节
|
||||
}
|
||||
|
||||
def cargo包裹信息(self):
|
||||
return {
|
||||
"count": 1, # number 是 包裹数量, 默认为1
|
||||
"weight": 4, # number 是 货物总重量,比如1.2,单位是千克(kg)
|
||||
"space_x": 1, # number 是 货物长度,比如20.0,单位是厘米(cm)
|
||||
"space_y": 1, # number 是 货物宽度,比如15.0,单位是厘米(cm)
|
||||
"space_z": 1, # number 是 货物高度,比如10.0,单位是厘米(cm)
|
||||
"detail_list": [
|
||||
{
|
||||
"name": "商品名称", # string 是 商品名,不超过128字节
|
||||
"count": 1, # number 是 商品数量
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
def shop商品信息(self, order物流商家侧订单号):
|
||||
订单对象 = order订单.objects.get(order物流订单号=order物流商家侧订单号)
|
||||
orderData = model_to_dict(instance=订单对象, fields=None)
|
||||
购物车商品列表 = pydash.get(obj=orderData, path="order购物车商品数据")
|
||||
detail_list = []
|
||||
for goods in 购物车商品列表:
|
||||
detail_list.append(
|
||||
{
|
||||
"goods_name": emoji.replace_emoji(pydash.get(obj=goods, path="goods_商品标题")),
|
||||
"goods_img_url": pydash.get(obj=goods, path="goods_sku_主图"),
|
||||
"goods_desc": emoji.replace_emoji(pydash.get(obj=goods, path="goods_商品品牌")),
|
||||
},
|
||||
)
|
||||
goods_count = pydash.get(obj=orderData, path="order购物车商品数量")
|
||||
shop商品信息数据 = {
|
||||
# "wxa_path": "/pages/OrderPages/OrderPages", # string 否 商家小程序的路径,建议为订单页面
|
||||
# "img_url": "", # string 否 商品缩略图 url;shop.detail_list为空则必传,shop.detail_list非空可不传。
|
||||
# "goods_name": "", # string 否 商品名称, 不超过128字节;shop.detail_list为空则必传,shop.detail_list非空可不传。
|
||||
# goods_count 否 商品详情列表,适配多商品场景,用以消息落地页展示。(新规范,新接入商家建议用此字段)
|
||||
"goods_count": goods_count, # 商品数量;shop.detail_list为空则必传。shop.detail_list非空可不传,默认取shop.detail_list的size
|
||||
"detail_list": detail_list,
|
||||
}
|
||||
return shop商品信息数据
|
||||
|
||||
def insured保价信息(self):
|
||||
insured保价信息数据 = {
|
||||
"use_insured": 0, # number 否 是否保价,0 表示不保价,1 表示保价
|
||||
"insured_value": 0, # number 否 保价金额,单位是分,比如: 10000 表示 100 元
|
||||
}
|
||||
return insured保价信息数据
|
||||
|
||||
def service服务类型(self):
|
||||
service服务类型数据 = {
|
||||
"service_type": 6, # number 是 服务类型ID,详见已经支持的快递公司基本信息
|
||||
"service_name": "标准快递", # string 是 服务名称,详见已经支持的快递公司基本信息
|
||||
}
|
||||
return service服务类型数据
|
||||
|
||||
def 生成最终完整的数据(self, order物流商家侧订单号):
|
||||
订单对象 = order订单.objects.get(order物流订单号=order物流商家侧订单号)
|
||||
收件人openid = 订单对象.order支付者openid
|
||||
完整数据 = {
|
||||
"add_source": 0, # 订单来源,0为小程序订单,2为App或H5订单,填2则不发送物流服务通知
|
||||
"order_id": order物流商家侧订单号,
|
||||
"openid": 收件人openid, # 是 用户openid,当add_source=2时无需填写(不发送物流服务通知)
|
||||
"delivery_id": self.ems_delivery_id, # 是 快递公司ID,
|
||||
"biz_id": self.ems_biz_id, # 快递客户编码或者现付编码
|
||||
"sender": self.ems收件人信息(order物流商家侧订单号=order物流商家侧订单号), # 收件人的信息
|
||||
"receiver": self.ems发件人信息(), # 发件人的信息
|
||||
"shop": self.shop商品信息(order物流商家侧订单号=order物流商家侧订单号), # 商品的信息
|
||||
"cargo": self.cargo包裹信息(), # 包果的信息
|
||||
"insured": self.insured保价信息(), # 保价信息
|
||||
"service": self.service服务类型(), # 服务类型
|
||||
}
|
||||
return 完整数据
|
||||
|
||||
def 绑定账号(self):
|
||||
"""请求接口,通过接口来实现绑定物流的账号, 也可以在小程序的管理后台来操作"""
|
||||
mini_token = sixToken.get_mini_token()
|
||||
uri = f"https://api.weixin.qq.com/cgi-bin/express/business/account/bind?access_token={mini_token}"
|
||||
ems数据 = {
|
||||
"type": "bind",
|
||||
"biz_id": "1100244903172",
|
||||
"delivery_id": "EMS",
|
||||
"password": "xiao@792282",
|
||||
}
|
||||
data = requests.post(url=uri, json=ems数据)
|
||||
print("🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 ", data.json())
|
||||
|
||||
def 获取支持的快递公司列表(self, ):
|
||||
"""请求接口,可以查询当前支持的物流公司列表"""
|
||||
mini_token = sixToken.get_mini_token()
|
||||
uri = f"https://api.weixin.qq.com/cgi-bin/express/business/delivery/getall?access_token={mini_token}"
|
||||
data = requests.get(url=uri, )
|
||||
emdData = orjson.loads(data.content.decode(encoding="utf-8"))
|
||||
print(emdData)
|
||||
|
||||
def 获取所有绑定的物流账号(self, ):
|
||||
"""请求接口,可以查询所有小程序已经绑定的物流账号"""
|
||||
mini_token = sixToken.get_mini_token()
|
||||
uri = f"https://api.weixin.qq.com/cgi-bin/express/business/account/getall?access_token={mini_token}"
|
||||
data = requests.get(url=uri)
|
||||
print(orjson.loads(data.content.decode(encoding="utf-8")))
|
||||
|
||||
def 生成运单(self, out_trade_no商家侧支付订单号: str):
|
||||
"""在实际项目中,这里的不是需要自己生成的"""
|
||||
# 需要的参数
|
||||
order订单对象 = order订单.objects.get(order支付商家侧订单号=out_trade_no商家侧支付订单号)
|
||||
print(order订单对象.order物流订单号)
|
||||
res = self.生成最终完整的数据(order物流商家侧订单号=order订单对象.order物流订单号)
|
||||
mini_token = sixToken.get_mini_token()
|
||||
运单请求接口 = f"https://api.weixin.qq.com/cgi-bin/express/business/order/add?access_token={mini_token}"
|
||||
json_完整的数据 = orjson.dumps(res).decode(encoding="utf-8")
|
||||
|
||||
data = requests.post(url=运单请求接口, data=json_完整的数据, headers={"Content-Type": "application/json; charset=utf-8"})
|
||||
waybill_id = pydash.get(obj=data.json(), path="waybill_id")
|
||||
order当前订单对象 = order订单.objects.get(order支付商家侧订单号=out_trade_no商家侧支付订单号, ) # 拿到当前的订单号生成的订单对象
|
||||
order当前订单对象.order物流订单号 = order订单对象.order物流订单号
|
||||
order当前订单对象.order物流公司订单编号 = waybill_id
|
||||
order当前订单对象.save()
|
||||
print(data.json())
|
||||
# 这里的作用是, 当订单生成了 就调用 小程序交易管理的发货管理接口, 自动录入订单号
|
||||
mini调用微信小程序交易管理服务录入订单接口.apply_async(kwargs={"order支付商家侧订单号": order当前订单对象.order支付商家侧订单号}, countdown=30)
|
||||
|
||||
def 模拟更新订单状态(self, order物流商家侧订单号):
|
||||
""""""
|
||||
|
||||
mini_token = sixToken.get_mini_token()
|
||||
milliseconds_int = ULID().milliseconds
|
||||
模拟更新物流状态 = f"https://api.weixin.qq.com/cgi-bin/express/business/test_update_order?access_token={mini_token}"
|
||||
模拟数据 = {
|
||||
"biz_id": self.ems_biz_id,
|
||||
"order_id": order物流商家侧订单号, # 商家生成的订单号
|
||||
"delivery_id": self.ems_delivery_id, #
|
||||
"waybill_id": "", # 物流订单号
|
||||
"action_time": milliseconds_int, # 这里是更新发的时间戳
|
||||
"action_type": 100001,
|
||||
"action_msg": "揽件成功",
|
||||
}
|
||||
data = requests.post(url=模拟更新物流状态,
|
||||
data=orjson.dumps(模拟数据, option=orjson.OPT_SORT_KEYS, ).decode(encoding="utf-8"))
|
||||
print("🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 ", data.json())
|
||||
|
||||
def 查询运单轨迹(self, order物流商家侧订单号):
|
||||
""""""
|
||||
查询对象 = order订单.objects.get(order物流订单号=order物流商家侧订单号, )
|
||||
print(查询对象)
|
||||
|
||||
def 更新物流快递数据(self, order物流公司订单编号, order物流订单号):
|
||||
""""""
|
||||
查询对象 = order订单.objects.get(order物流公司订单编号=order物流公司订单编号, order物流订单号=order物流订单号)
|
||||
mini_token = sixToken.get_mini_token()
|
||||
emsGetPathData = requests.post(url="https://api.weixin.qq.com/cgi-bin/express/business/path/get",
|
||||
params={"access_token": mini_token, },
|
||||
json={
|
||||
"openid": 查询对象.order支付者openid,
|
||||
"delivery_id": self.ems_delivery_id, "waybill_id": 查询对象.order物流公司订单编号,
|
||||
},
|
||||
)
|
||||
resData = orjson.loads(emsGetPathData.content.decode(encoding="utf-8"))
|
||||
print("请求物流更新接口返回数据", resData)
|
||||
# 根据实时的状态,来更新当前的物流状态
|
||||
action_type = pydash.get(obj=resData, path="path_item_list.0.action_type")
|
||||
path_item_list = pydash.get(obj=resData, path="path_item_list")
|
||||
if action_type == 100001:
|
||||
# 100001 揽件阶段-揽件成功
|
||||
查询对象.order物流数据 = resData
|
||||
查询对象.order物流状态 = "揽件成功"
|
||||
查询对象.save()
|
||||
if action_type == 100002:
|
||||
# 100002 揽件阶段-揽件失败
|
||||
查询对象.order物流数据 = resData
|
||||
查询对象.order物流状态 = "揽件失败"
|
||||
查询对象.save()
|
||||
if action_type == 100003:
|
||||
# 100003 揽件阶段-分配业务员
|
||||
查询对象.order物流数据 = resData
|
||||
查询对象.order物流状态 = "分配业务员"
|
||||
查询对象.save()
|
||||
if action_type == 200001:
|
||||
# 200001 运输阶段-更新运输轨迹
|
||||
查询对象.order物流数据 = resData
|
||||
查询对象.order物流状态 = "更新运输轨迹"
|
||||
查询对象.save()
|
||||
if action_type == 300002:
|
||||
# 300002 派送阶段-开始派送
|
||||
查询对象.order物流数据 = resData
|
||||
查询对象.order物流状态 = "开始派送"
|
||||
查询对象.save()
|
||||
if action_type == 300003:
|
||||
# 300003 派送阶段-签收成功
|
||||
查询对象.order物流数据 = resData
|
||||
查询对象.order物流状态 = "签收成功"
|
||||
查询对象.save()
|
||||
# 当用户确认签收以后: 调用微信交易管理服务,推送用户确认的通知
|
||||
miniTransaction交易管理服务工具().确认收货提醒(order支付微信官方订单号=查询对象.order支付微信官方订单号, order物流数据=path_item_list)
|
||||
if action_type == 300004:
|
||||
# 300004 派送阶段-签收失败
|
||||
查询对象.order物流数据 = resData
|
||||
查询对象.order物流状态 = "签收失败"
|
||||
查询对象.save()
|
||||
if action_type == 400001:
|
||||
# 400001 异常阶段-订单取消
|
||||
查询对象.order物流数据 = resData
|
||||
查询对象.order物流状态 = "订单取消"
|
||||
查询对象.save()
|
||||
if action_type == 400002:
|
||||
# 400002 异常阶段-订单滞留
|
||||
查询对象.order物流数据 = resData
|
||||
查询对象.order物流状态 = "订单滞留"
|
||||
查询对象.save()
|
||||
|
||||
def 确认收货(self, order支付微信侧订单号):
|
||||
"""调用的时机为: 微信小程序官方的消息推送, 当用户确认了收货 或者 时间到了自动确认时,会调用,主要就是更新 下单商品的状态 """
|
||||
订单更新对象 = order订单.objects.get(order支付微信官方订单号=order支付微信侧订单号)
|
||||
订单更新对象.order物流状态 = "已确认"
|
||||
订单更新对象.save()
|
||||
# 备注一下返回数据类型
|
||||
responseDataa = {'ToUserName': 'gh_f754c53eb962', 'FromUserName': 'ohQkD7HmLYEYrFjFau6juOhduRU8', 'CreateTime': 1784666355, 'MsgType': 'event', 'Event': 'trade_manage_order_settlement',
|
||||
'transaction_id': '4200003114202607213777723688', 'merchant_id': '1700383419', 'sub_merchant_id': '', 'merchant_trade_no': 'OAT01KY1BZ3T9SFSSRNS10R95B5XH', 'pay_time': 1784605024,
|
||||
'shipped_time': 1784666354,
|
||||
'estimated_settlement_time': 1784839154
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 当脚本直接运行时执行的代码
|
||||
ems邮政物流工具().生成运单(out_trade_no商家侧支付订单号="")
|
||||
@@ -0,0 +1,11 @@
|
||||
class wecom推广二维码:
|
||||
@classmethod
|
||||
def 设置授权应用可见范围(cls):
|
||||
"""
|
||||
调用该接口前提是开启通讯录迁移,收到授权成功通知后可调用。企业注册初始化安装应用后,应用默认可见范围为根部门。
|
||||
如需修改应用可见范围,服务商可以调用该接口设置授权应用的可见范围。
|
||||
该接口只能使用注册完成回调事件或者查询注册状态返回的access_token,
|
||||
调用设置通讯录同步完成后或者access_token超过30分钟失效(即解除通讯录锁定状态)则不能继续调用该接口
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
@@ -0,0 +1,63 @@
|
||||
import dataclasses
|
||||
|
||||
import jmespath
|
||||
import pydash
|
||||
import requests
|
||||
from asgiref.sync import async_to_sync
|
||||
from channels.layers import get_channel_layer
|
||||
|
||||
from sixGuoDjango import redis_5
|
||||
from wechat_项目工具包.企业微信工具包.客户管理 import wecom客户管理
|
||||
from wechat_项目工具包.企业微信工具包.消息推送工具 import wecom消息推送
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class six企微回调业务处理方法():
|
||||
pass
|
||||
|
||||
def 添加企业客户事件(self, as_data):
|
||||
""""""
|
||||
"""
|
||||
添加企业客户事件
|
||||
参数 说明 配置了客户联系功能的成员添加外部联系人时,回调该事件
|
||||
ToUserName 企业微信CorpID
|
||||
FromUserName 此事件该值固定为sys,表示该消息由系统生成
|
||||
CreateTime 消息创建时间 (整型)
|
||||
MsgType 消息的类型,此时固定为event
|
||||
Event 事件的类型,此时固定为change_external_contact
|
||||
ChangeType 此时固定为add_external_contact
|
||||
UserID 企业服务人员的UserID
|
||||
ExternalUserID 外部联系人的userid,注意不是企业成员的账号
|
||||
State 添加此用户的「联系我」方式配置的state参数,或在获客链接中指定的customer_channel参数,可用于识别添加此用户的渠道
|
||||
WelcomeCode 欢迎语code,可用于发送欢迎语
|
||||
"""
|
||||
# 这是用户在添加企微好友成功时的回调信息;
|
||||
ToUserName = pydash.get(obj=as_data, path="xml.ToUserName") # toUser 企业微信CorpID
|
||||
FromUserName = pydash.get(obj=as_data, path="xml.FromUserName") # sys 此事件该值固定为sys,表示该消息由系统生成
|
||||
CreateTime = pydash.get(obj=as_data, path="xml.CreateTime") # 1403610513 消息创建时间 (整型)
|
||||
MsgType = pydash.get(obj=as_data, path="xml.MsgType") # event 消息的类型,此时固定为event
|
||||
Event = pydash.get(obj=as_data, path="xml.Event") # change_external_contact 事件的类型,此时固定为change_external_contact
|
||||
ChangeType = pydash.get(obj=as_data, path="xml.ChangeType") # add_external_contact 此时固定为add_external_contact
|
||||
UserID = pydash.get(obj=as_data, path="xml.UserID") # UserID 企业服务人员的UserID
|
||||
ExternalUserID = pydash.get(obj=as_data, path="xml.ExternalUserID", ) # woAJ2GCAAAXtWyujaWJHDDGi0mAAAA 外部联系人的userid,注意不是企业成员的账号
|
||||
State = pydash.get(obj=as_data, path="xml.State", ) # teststate 添加此用户的「联系我」方式配置的state参数,或在获客链接中指定的customer_channel参数,可用于识别添加此用户的渠道
|
||||
WelcomeCode = pydash.get(obj=as_data, path="xml.WelcomeCode") # WELCOMECODE 欢迎语code,可用于发送欢迎语
|
||||
# 发送欢迎语
|
||||
print("添加企业客户事件")
|
||||
|
||||
res_welcom_code = wecom消息推送.发送新客户欢迎语(CALLBACK_CODE=WelcomeCode)
|
||||
# 获取用户的 unionid (主要就是获取用户的unionid的值)
|
||||
res = wecom客户管理.获取客户详情(external_userid=ExternalUserID)
|
||||
print(res)
|
||||
unionid = jmespath.search(data=res, expression="external_contact.unionid")
|
||||
print(unionid)
|
||||
wecom_pay_data = requests.post(url="https://xiao.sixgou.com/demopay/transfer/", json={"unionid": unionid})
|
||||
# 存储数据(用户添加后的数据)
|
||||
print("打印数据类型", wecom_pay_data.json())
|
||||
print("打印数据类型", type(wecom_pay_data.json()))
|
||||
payData = wecom_pay_data.json()
|
||||
channels_name = redis_5.get(name=unionid)
|
||||
default_channel_layer = get_channel_layer(alias="default")
|
||||
async_to_sync(awaitable=default_channel_layer.send)(channel=channels_name, message={"type": "chat_subMsg", "data": payData})
|
||||
pass
|
||||
# 调用发红包的功能
|
||||
@@ -0,0 +1,168 @@
|
||||
"""客户标签管理工具"""
|
||||
import os
|
||||
|
||||
import django
|
||||
|
||||
from sixGuoDjango.settings import BASE_DIR
|
||||
from wechat_token.token工具包 import sixToken
|
||||
|
||||
os.environ.setdefault(key="DJANGO_SETTINGS_MODULE", value=f"{BASE_DIR.name}.settings")
|
||||
django.setup()
|
||||
|
||||
import datetime
|
||||
|
||||
import jmespath
|
||||
import requests
|
||||
|
||||
from requests import post
|
||||
|
||||
|
||||
class wecom管理企业标签:
|
||||
"""
|
||||
管理企业标签
|
||||
最后更新:2023/12/01
|
||||
企业客户标签是针对企业的外部联系人进行标记和分类的标签,由企业统一配置后,企业成员可使用此标签对客户进行标记。
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def 获取企业标签库(cls):
|
||||
"""
|
||||
在这里返回结果为:企业微信创建的所有的企业标签,不包含个人标签
|
||||
企业可通过此接口获取企业客户标签详情。请求方式: POST(HTTP)
|
||||
"""
|
||||
pass
|
||||
wecom_token = sixToken.get_wecom_token()
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_corp_tag_list?access_token={wecom_token}"
|
||||
data = post(url=uri).json()
|
||||
print(data)
|
||||
tag_group = jmespath.search(data=data, expression="tag_group")
|
||||
for tag in tag_group:
|
||||
print(tag)
|
||||
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def 添加企业客户标签(cls):
|
||||
"""
|
||||
企业可通过此接口向客户标签库中添加新的标签组和标签,每个企业最多可配置10000个企业标签。
|
||||
注意:
|
||||
1. 如果要向指定的标签组下添加标签,需要填写group_id参数;
|
||||
2. 如果要创建一个全新的标签组以及标签,则需要通过group_name参数指定新标签组名称,
|
||||
3. 如果填写的groupname已经存在,则会在此标签组下新建标签。
|
||||
4. 如果填写了group_id参数,则group_name和标签组的order参数会被忽略。
|
||||
不支持创建空标签组。
|
||||
标签组内的标签不可同名,如果传入多个同名标签,则只会创建一个。
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
wecom_token = sixToken.get_wecom_token()
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_corp_tag?access_token={wecom_token}"
|
||||
请求数据示例 = {
|
||||
"group_id": "etj4zANAAA1Zh_RyLIpjOr6IbuMPRB8g", # 否 标签组id
|
||||
# "group_name": "泰州市", # 否 标签组名称,最长为30个字符
|
||||
"order": 1,
|
||||
"tag": [
|
||||
{
|
||||
"name": "未知", # 是 添加的标签名称,最长为30个字符
|
||||
"order": 1, # 否 标签次序值。order值大的排序靠前。有效的值范围是[0, 2^32)
|
||||
},
|
||||
],
|
||||
# "agentid": 1000014, # 授权方安装的应用agentid。仅旧的第三方多应用套件需要填此参数
|
||||
}
|
||||
data = post(url=uri, json=请求数据示例).json()
|
||||
print(data)
|
||||
|
||||
@classmethod
|
||||
def 编辑客户企业标签(cls, userid: str, external_userid: str, add_tag: str):
|
||||
"""
|
||||
企业可通过此接口为指定成员的客户添加上由企业统一配置的标签;注意,每个成员对同一个客户最多可以添加3000个标签。
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
wecom_token = sixToken.get_wecom_token()
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/mark_tag?access_token={wecom_token}"
|
||||
请求数据示例 = {
|
||||
# "userid": "YuanBao1975", # 是 添加外部联系人的userid
|
||||
"userid": userid, # 是 添加外部联系人的userid
|
||||
# "external_userid": "wmj4zANAAAGlCZ2zNRhu1qzEMSBF0a9gs", # 这里测试的是葫芦娃本人的标签
|
||||
"external_userid": external_userid, # 这里测试的是葫芦娃本人的标签
|
||||
# "add_tag": ["etj4zANAAAGtArlV00IwBWzIqinXf5vQ"], # 要添加的标签列表一定要填写🆔(对某一个用户添加某些标签)
|
||||
"add_tag": [add_tag], # 要添加的标签列表一定要填写🆔(对某一个用户添加某些标签)
|
||||
# "remove_tag": [] # 要移除的标签列表(对某一个用户移除某些标签)
|
||||
}
|
||||
data = post(url=uri, json=请求数据示例).json()
|
||||
print(data)
|
||||
|
||||
@classmethod
|
||||
def 更新所有的企业标签并存储到数据库(cls):
|
||||
pass
|
||||
data = cls.获取企业标签库()
|
||||
tag_group = jmespath.search(data=data, expression="tag_group", )
|
||||
for tag_list in tag_group:
|
||||
print(tag_list)
|
||||
# tag_list = jmespath.search(data=tag_list, expression="tag", )
|
||||
group_id = jmespath.search(data=tag_list, expression="group_id", )
|
||||
group_name = jmespath.search(data=tag_list, expression="group_name", )
|
||||
create_time = jmespath.search(data=tag_list, expression="create_time", )
|
||||
# create_time = pydash.get(obj=tag_list, path="create_time", )
|
||||
# 需要将时间戳转为时间格式
|
||||
print(create_time, "========", )
|
||||
create_time_1 = datetime.datetime.fromtimestamp(create_time)
|
||||
#
|
||||
|
||||
tag_child_list = jmespath.search(data=tag_list, expression="tag", )
|
||||
|
||||
@classmethod
|
||||
def 更新所有的用户性别组标签(cls):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def 打标签性别(cls, userid: str, external_userid: str, gender: int):
|
||||
"""
|
||||
:param gender:
|
||||
:type gender:
|
||||
:param userid:
|
||||
:type userid:
|
||||
:param external_userid:
|
||||
:type external_userid:
|
||||
"""
|
||||
print("单独更新用户性别标签")
|
||||
if gender == 1:
|
||||
cls.编辑客户企业标签(
|
||||
userid=userid, external_userid=external_userid,
|
||||
add_tag="et74kdKAAAO9rCHIG-GcT7ZU_gsTslkg",
|
||||
)
|
||||
if gender == 2:
|
||||
cls.编辑客户企业标签(
|
||||
userid=userid, external_userid=external_userid,
|
||||
add_tag="et74kdKAAAJUou62OZwyu3LeavfmxAhw",
|
||||
)
|
||||
if gender == 0:
|
||||
cls.编辑客户企业标签(
|
||||
userid=userid, external_userid=external_userid,
|
||||
add_tag="et74kdKAAAI_VNWc-Gzakmo_jGnK-Glw",
|
||||
)
|
||||
|
||||
def 给个微好友打标签(self):
|
||||
pass
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/mark_tag"
|
||||
请求数据 = {
|
||||
"userid": "YuanBao",
|
||||
"external_userid": "wm74kdKAAAwE4T5vvLoUgHq9dEu7eTgA", # 给小豆豆测试打标签
|
||||
"add_tag": ["et74kdKAAAO9rCHIG-GcT7ZU_gsTslkg"], # 测试标签ID
|
||||
# "remove_tag": ["et74kdKAAAO9rCHIG-GcT7ZU_gsTslkg"] # 删除企业微信好友的 标签
|
||||
}
|
||||
wecom_token = sixToken.get_wecom_token()
|
||||
response_data = requests.post(url=uri, params={"access_token": wecom_token}, json=请求数据)
|
||||
print(response_data.json())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
wecom管理企业标签.获取企业标签库()
|
||||
# wecom管理企业标签.添加企业客户标签()
|
||||
# wecom管理企业标签.更新所有的用户性别组标签()
|
||||
# wecom管理企业标签.更新所有的企业标签并存储到数据库()
|
||||
# wecom管理企业标签.获取企业标签库()
|
||||
# wecom管理企业标签().给个微好友打标签()
|
||||
@@ -0,0 +1,220 @@
|
||||
import io
|
||||
from typing import List, Literal
|
||||
|
||||
import pydash
|
||||
import requests
|
||||
from pydantic import BaseModel
|
||||
|
||||
from wechat_token.token工具包 import sixToken
|
||||
from wechat_数据库.企业微信数据库.企微用户表 import wecom企微用户
|
||||
from wechat_数据库.用户数据库.小程序登陆用户 import weiuser
|
||||
from wechat_项目工具包.数据库的工具包.更新用户积分 import miniUser用户角色
|
||||
from 第三方工具目录.腾讯云 import oss上传腾讯云图片
|
||||
|
||||
|
||||
class wecomData客户列表响应(BaseModel):
|
||||
errcode: int
|
||||
errmsg: str
|
||||
external_userid: List[str]
|
||||
|
||||
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
class wecomData外部联系人基本信息(BaseModel):
|
||||
"""外部联系人基本信息"""
|
||||
external_userid: str
|
||||
name: str
|
||||
type: int
|
||||
avatar: str
|
||||
gender: int
|
||||
unionid: Optional[str] = None
|
||||
|
||||
|
||||
class wecomData跟进人标签(BaseModel):
|
||||
"""跟进人标签"""
|
||||
group_name: str
|
||||
tag_name: str
|
||||
type: int
|
||||
tag_id: Optional[str] = None
|
||||
|
||||
|
||||
class wecomData跟进成员信息(BaseModel):
|
||||
"""跟进成员信息"""
|
||||
userid: str
|
||||
remark: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
createtime: int
|
||||
state: Optional[str] = None
|
||||
tags: List[wecomData跟进人标签] = []
|
||||
remark_mobiles: List[str] = []
|
||||
remark_corp_name: Optional[str] = None
|
||||
add_way: int
|
||||
oper_userid: str
|
||||
|
||||
|
||||
class wecomData获取客户详情响应(BaseModel):
|
||||
"""获取客户详情接口返回"""
|
||||
errcode: int
|
||||
errmsg: str
|
||||
external_contact: Optional[wecomData外部联系人基本信息] = None
|
||||
follow_user: List[wecomData跟进成员信息] = []
|
||||
|
||||
|
||||
six六果对象xiao, six是否新创建值 = weiuser.objects.update_or_create(unionid="六果",
|
||||
defaults={
|
||||
"user_IP地址": None, "user_昵称": "六果", "user_用户当前的等级": 0, "user_省份": "六果", "user_城市": "六果", "user_区县": "六果",
|
||||
"user_街道信息": "六果", "user_头像": "https://hudie.xiao.band/%E6%9C%AA%E5%91%BD%E5%90%8D.png", "user_绑定企微usrID账号": "六果", "user_角色权限": "六果", "external_userid": "六果",
|
||||
"parent_id": "六果", "mini_openid": "wxf612e825377328f0", "wechat_openid": "六果", "wecom_openid": "wwbe6369dc26a9d83a", "codeConfigId": "六果", "codeRemark": "六果",
|
||||
"codeQrCode": "https://hudie.xiao.band/mini.jpg",
|
||||
"user_企微角色权限": "六果", "user_绑定企微UNIONID": "六果"
|
||||
})
|
||||
|
||||
|
||||
class wecom客户好友管理(BaseModel):
|
||||
# demo_必填: int = Field(..., description="描述") # 必传属发生
|
||||
# demo_可选: Optional[int] = None # 可选值
|
||||
# demo_默认值: int = Field(default=100001, description="描述") # 有默认值
|
||||
# demo_联合类型: Literal["xiao", "zizi"] = Field(..., description="描述")
|
||||
|
||||
def 企微原用户处理接口(self, data):
|
||||
""""""
|
||||
state上级用户 = pydash.get(obj=data, path="state", default="六果") # 上级分享者的uniond值 这里是企微二维码绑定的 uninoid
|
||||
external_userid = pydash.get(obj=data, path="external_userid")
|
||||
unionid下级用户 = pydash.get(obj=data, path="unionid")
|
||||
print(f"state上级用户===>{state上级用户}", f"unionid下级用户===>{unionid下级用户}")
|
||||
上级用户, 上级用户状态 = weiuser.objects.get_or_create(unionid=state上级用户) # 如果state的状态不为None:则创建这个用户
|
||||
下级用户, 下级用户状态 = weiuser.objects.get_or_create(unionid=unionid下级用户)
|
||||
six六果对象, sixBool = weiuser.objects.get_or_create(unionid="六果")
|
||||
if 上级用户.parent is None:
|
||||
# 当这里企微号的state绑定的小程序用户的时候 的 上级用户为None的时候
|
||||
上级用户.parent = six六果对象
|
||||
上级用户.external_userid = external_userid
|
||||
上级用户.user_用户当前的等级 = six六果对象.user_用户当前的等级 + 1
|
||||
上级用户.codeQrCode = six六果对象.codeQrCode
|
||||
上级用户.codeRemark = six六果对象.codeRemark
|
||||
上级用户.codeConfigId = six六果对象.codeConfigId
|
||||
上级用户.user_绑定企微UNIONID = six六果对象.user_绑定企微UNIONID
|
||||
上级用户.save()
|
||||
# # 第二步就只需要查询,如果下级用户已经有上级了就直接忽略
|
||||
if 上级用户.parent is not None:
|
||||
print("这里是上上级用户不为None的时候", )
|
||||
|
||||
# 还需要判断一下,下级用户的 父元素为six公司对象的时候,还得更新一下当前的数据,因为这个用户原来就是属于 企微员工的绑定者
|
||||
if 下级用户.parent == six六果对象 and 下级用户.user_企微角色权限 != miniUser用户角色().企微员工:
|
||||
print(下级用户, "🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎🍎判断成功了")
|
||||
下级用户.parent = 上级用户
|
||||
下级用户.user_用户当前的等级 = 上级用户.user_用户当前的等级 + 1
|
||||
下级用户.user_绑定企微UNIONID = 上级用户.user_绑定企微UNIONID
|
||||
下级用户.codeConfigId = 上级用户.codeConfigId
|
||||
下级用户.codeQrCode = 上级用户.codeQrCode
|
||||
下级用户.codeRemark = 上级用户.codeRemark
|
||||
下级用户.user_绑定企微usrID账号 = 上级用户.user_绑定企微usrID账号
|
||||
下级用户.save()
|
||||
if 下级用户.parent is not None:
|
||||
# 这里判断出下级用户有上级,直接原样处理,不做任何的更改状态
|
||||
print("下级用户的父不为不为不为元素为None的时候", 上级用户)
|
||||
下级用户.external_userid = external_userid
|
||||
下级用户.save()
|
||||
if 下级用户.parent is None:
|
||||
print("下级用户的父元素为None的时候", 上级用户)
|
||||
下级用户.parent = 上级用户
|
||||
下级用户.user_用户当前的等级 = 上级用户.user_用户当前的等级 + 1
|
||||
下级用户.external_userid = external_userid
|
||||
下级用户.codeQrCode = 上级用户.codeQrCode
|
||||
下级用户.codeRemark = 上级用户.codeRemark
|
||||
下级用户.codeConfigId = 上级用户.codeConfigId
|
||||
下级用户.user_绑定企微UNIONID = 上级用户.user_绑定企微UNIONID
|
||||
下级用户.save()
|
||||
|
||||
def 获取客户列表(self, userid: Literal["YuanBao1975"]) -> wecomData客户列表响应:
|
||||
"""根据企微员工的ID,一次获取当前企微员所有的好友列表"""
|
||||
wecom_token = sixToken.get_wecom_token()
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/list"
|
||||
json_data = requests.get(url=uri, params={"access_token": wecom_token, "userid": userid})
|
||||
# print("获取客户列表获取客户列表获取客户列表获取客户列表获取客户列表获取客户列表获取客户列表", json_data.json())
|
||||
get_data = wecomData客户列表响应(**json_data.json())
|
||||
return get_data.model_dump()
|
||||
|
||||
def 获取客户详情(self, external_userid) -> wecomData获取客户详情响应:
|
||||
"""通过 external_userid 获取用户的详情,最主要的就是拿到用户的 unionid """
|
||||
wecom_token = sixToken.get_wecom_token()
|
||||
uri = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get"
|
||||
data = requests.get(url=uri, params={"access_token": wecom_token, "external_userid": external_userid})
|
||||
print("\n🍎 🍏 🍊获取客户详情 🍎 🍏 \n", data.json())
|
||||
return wecomData获取客户详情响应(**data.json())
|
||||
|
||||
def 批量获取客户详情并存储到数据库(self, state企微员工创建时添加的唯一值, userid企微员工账号ID: Literal["YuanBao1975"]):
|
||||
"""根据企微员工的ID,获取当前企微员添加的所有的外 部联系人好友,并存储到数据库"""
|
||||
|
||||
def request_recursive(userid_list=None, cursor="", ):
|
||||
""""""
|
||||
# 初始化参数
|
||||
wecom_token_inner = sixToken.get_wecom_token()
|
||||
# 构建请求参数
|
||||
uri = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/batch/get_by_user"
|
||||
json_data = {"userid_list": userid_list, "limit": 200, "cursor": cursor}
|
||||
|
||||
# 发送请求
|
||||
try:
|
||||
data = requests.post(url=uri, params={"access_token": wecom_token_inner}, json=json_data, )
|
||||
response_json = data.json()
|
||||
# 提取当前页的外部联系人
|
||||
# 获取下一页游标
|
||||
external_contact_list = pydash.get(obj=response_json, path="external_contact_list")
|
||||
next_cursor = pydash.get(obj=response_json, path="next_cursor", default="")
|
||||
|
||||
print(f"当前获取到 {len(external_contact_list)} 个外部联系人,总计 {len(external_contact_list)} 个")
|
||||
# 如果还有下一页,递归调用
|
||||
for post_data in external_contact_list:
|
||||
userid = pydash.get(obj=post_data, path="follow_info.userid") # 添加了此外部联系人的企业成员userid 如 HuDie_1
|
||||
remark = pydash.get(obj=post_data, path="follow_info.remark") # 该成员对此微信客户备注的企业名称(仅微信客户有该字段)
|
||||
description = pydash.get(obj=post_data, path="follow_info.description")
|
||||
createtime = pydash.get(obj=post_data, path="follow_info.createtime")
|
||||
state = pydash.get(obj=post_data, path="follow_info.state") # 企业自定义的state参数,用于区分客户具体是通过哪个「联系我」或获客链接添加;由企业通过创建「联系我」或在获客链接中添加customer_channel参数进行指定
|
||||
tag_id = pydash.get(obj=post_data, path="follow_info.tag_id")
|
||||
remark_mobiles = pydash.get(obj=post_data, path="follow_info.remark_mobiles") # 该成员对此客户备注的手机号码,第三方应用和代开发应用均不可获取,上游企业不可获取下游企业客户该字段
|
||||
add_way = pydash.get(obj=post_data, path="follow_info.add_way") # 该成员添加此客户的来源add_way为10时,对应的视频号信息
|
||||
oper_userid = pydash.get(obj=post_data, path="follow_info.oper_userid") # 发起添加的userid,如果成员主动添加,为成员的userid;如果是客户主动添加,则为客户的外部联系人userid;如果是内部成员共享/管理员分配,则为对应的成员/管理员userid
|
||||
external_userid = pydash.get(obj=post_data, path="external_contact.external_userid") # 外部联系人的userid
|
||||
name = pydash.get(obj=post_data, path="external_contact.name") # 外部联系人的名称[注1]
|
||||
type_data = pydash.get(obj=post_data, path="external_contact.type") # 外部联系人的类型,1表示该外部联系人是微信用户,2表示该外部联系人是企业微信用户
|
||||
avatar = pydash.get(obj=post_data, path="external_contact.avatar") # 外部联系人头像。第三方应用和代开发应用均不可获取,上游企业不可获取下游企业客户该字段
|
||||
gender = pydash.get(obj=post_data, path="external_contact.gender") # 外部联系人性别 0-未知 1-男性 2-女性。
|
||||
unionid = pydash.get(obj=post_data, path="external_contact.unionid") # 外部联系人在微信开放平台的唯一身份标识(微信unionid),
|
||||
print("看看这里的值是多少看看这里的值是多少看看这里的值是多少看看这里的值是多少看看这里的值是多少看看这里的值是多少看看这里的值是多少看看这里的值是多少", state)
|
||||
if avatar and type_data == 1:
|
||||
img_file = requests.get(url=avatar).content
|
||||
src = oss上传腾讯云图片.上传图片(key=f"{userid}/{name}.png", file=io.BytesIO(img_file), )
|
||||
avatar = src
|
||||
# time.sleep( 1 )
|
||||
# avatar = "https://hudie.xiao.band/%E6%9C%AA%E5%91%BD%E5%90%8D.png"
|
||||
if bool(avatar) == False and type_data == 1:
|
||||
avatar = "https://hudie.xiao.band/%E6%9C%AA%E5%91%BD%E5%90%8D.png"
|
||||
if type_data == 1:
|
||||
weiuser.objects.update_or_create(unionid=unionid, defaults={"user_头像": avatar, "user_昵称": name, "external_userid": external_userid, })
|
||||
wecomData = {"state": state企微员工创建时添加的唯一值, "external_userid": external_userid, "unionid": unionid}
|
||||
self.企微原用户处理接口(data=wecomData)
|
||||
wecom企微用户.objects.update_or_create(wecom企微ID=userid, wecom企微对好友添加的手机号码=remark_mobiles, wecom企微好友添加方式=oper_userid, wecom企微状态State=state企微员工创建时添加的唯一值, wecom对好友打的标签=tag_id,
|
||||
wecom对好友的备注信息=description, wecom对好友的备注名称=remark, wecom对好友的添加时间=createtime, wecom好友头像=avatar, wecom好友性别=gender, wecom好友昵称=name, wecom好友的ID=external_userid,
|
||||
wecom好友UNIONID=unionid, )
|
||||
|
||||
if next_cursor:
|
||||
print(f"发现下一页,游标: {next_cursor}") # 递归调用
|
||||
request_recursive(userid_list=userid_list, cursor=next_cursor, )
|
||||
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"网络请求异常: {e}", )
|
||||
except Exception as e:
|
||||
print(f"处理异常: {e}")
|
||||
|
||||
request_recursive(userid_list=[userid企微员工账号ID], )
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
wai_external_userid = "wmj4zANAAAGlCZ2zNRhu1qzEMSBF0a9g"
|
||||
# data_wecom_客户详情数据 = wecom客户好友管理().获取客户详情(external_userid=wai_external_userid)
|
||||
wecom客户好友管理().批量获取客户详情并存储到数据库(state企微员工创建时添加的唯一值="oy4Jw6A1T3WkKOOhMxcbQTDSM8rs", userid企微员工账号ID="YuanBao1975") # 此方法会直接更新数据库并且保存数据
|
||||
@@ -0,0 +1,123 @@
|
||||
"""客户群管理工具"""
|
||||
import os
|
||||
|
||||
import django
|
||||
from pydantic import BaseModel
|
||||
|
||||
from sixGuoDjango.settings import BASE_DIR
|
||||
|
||||
os.environ.setdefault(key="DJANGO_SETTINGS_MODULE", value=f"{BASE_DIR.name}.settings")
|
||||
django.setup()
|
||||
|
||||
from wechat_token.token工具包 import sixToken
|
||||
|
||||
from requests import post
|
||||
|
||||
|
||||
# class emsParameters(BaseModel):
|
||||
# demo_必填: int = Field(..., description="描述") # 必传属发生
|
||||
# demo_可选: Optional[int] = None # 可选值
|
||||
# demo_默认值: int = Field(default=100001, description="描述") # 有默认值
|
||||
# demo_联合类型: Literal["xiao", "zizi"] = Field(..., description="描述")
|
||||
#
|
||||
|
||||
|
||||
class wecom客户群管理(BaseModel):
|
||||
"""类注释"""
|
||||
pass
|
||||
|
||||
def get获取客户群列表(self):
|
||||
"""
|
||||
该接口用于获取配置过客户群管理的客户群列表。
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
wecom_token = sixToken.get_wecom_token()
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/groupchat/list?access_token={wecom_token}"
|
||||
请求数据示例 = {
|
||||
# "status_filter": 0,
|
||||
# "owner_filter": {
|
||||
# "userid_list": ["abel"]
|
||||
# },
|
||||
# "cursor": "r9FqSqsI8fgNbHLHE5QoCP50UIg2cFQbfma3l2QsmwI",
|
||||
"limit": 10
|
||||
}
|
||||
data = post(url=uri, json=请求数据示例).json()
|
||||
print(data)
|
||||
|
||||
def get获取客户群详情(self, chat_id群号ID):
|
||||
"""
|
||||
通过客户群ID,获取详情。包括群名、群成员列表、群成员入群时间、入群方式。(客户群是由具有客户群使用权限的成员创建的外部群)
|
||||
需注意的是,如果发生群信息变动,会立即收到群变更事件,但是部分信息是异步处理,可能需要等一段时间调此接口才能得到最新结果
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
wecom_token = sixToken.get_wecom_token()
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/groupchat/get?access_token={wecom_token}"
|
||||
请求数据示例 = {
|
||||
"chat_id": chat_id群号ID, # 是 客户群ID
|
||||
"need_name": 1, # 否 是否需要返回群成员的名字group_chat.member_list.name。0-不返回;1-返回。默认不返回
|
||||
}
|
||||
data = post(url=uri, json=请求数据示例).json()
|
||||
print(data)
|
||||
|
||||
def 客户群opengid转换(self):
|
||||
"""
|
||||
用户在微信里的客户群里打开小程序时,某些场景下可以获取到群的opengid,如果该群是企业微信的客户群,则企业或第三方可以调用此接口将一个opengid转换为客户群chat_id
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
wecom_token = sixToken.get_wecom_token()
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/opengid_to_chatid?access_token={wecom_token}"
|
||||
请求数据示例 = {
|
||||
"opengid": "oAAAAAAA", # 是 小程序在微信获取到的群ID,参见wx.getGroupEnterInfo
|
||||
}
|
||||
data = post(url=uri, json=请求数据示例).json()
|
||||
print(data)
|
||||
|
||||
def 入群欢迎语素材管理(self):
|
||||
"""
|
||||
企业可通过此API向企业的入群欢迎语素材库中添加素材。每个企业的入群欢迎语素材库中,最多容纳100个素材。
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
wecom_token = sixToken.get_wecom_token()
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/group_welcome_template/add?access_token={wecom_token}"
|
||||
请求数据示例 = {
|
||||
"text": {
|
||||
"content": "亲爱的%NICKNAME%用户,你好"
|
||||
},
|
||||
"image": {
|
||||
"media_id": "MEDIA_ID",
|
||||
"pic_url": "http://p.qpic.cn/pic_wework/3474110808/7a6344sdadfwehe42060/0"
|
||||
},
|
||||
"link": {
|
||||
"title": "消息标题",
|
||||
"picurl": "https://example.pic.com/path",
|
||||
"desc": "消息描述",
|
||||
"url": "https://example.link.com/path"
|
||||
},
|
||||
"miniprogram": {
|
||||
"title": "消息标题",
|
||||
"pic_media_id": "MEDIA_ID",
|
||||
"appid": "wx8bd80126147dfAAA",
|
||||
"page": "/path/index"
|
||||
},
|
||||
"file": {
|
||||
"media_id": "1Yv-zXfHjSjU-7LH-GwtYqDGS-zz6w22KmWAT5COgP7o"
|
||||
},
|
||||
"video": {
|
||||
"media_id": "MEDIA_ID"
|
||||
},
|
||||
"agentid": 1000014,
|
||||
"notify": 1
|
||||
}
|
||||
data = post(url=uri, json=请求数据示例).json()
|
||||
print(data)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
wecom客户群管理().get获取客户群列表()
|
||||
wecom客户群管理().get获取客户群详情(chat_id群号ID="wrj4zANAAAmHh0Yqe23edo4TI462Bhpg")
|
||||
@@ -0,0 +1,380 @@
|
||||
import os
|
||||
|
||||
import django
|
||||
|
||||
from sixGuoDjango.settings import BASE_DIR
|
||||
from wechat_token.token工具包 import sixToken
|
||||
|
||||
|
||||
os.environ.setdefault( key="DJANGO_SETTINGS_MODULE", value=f"{BASE_DIR.name}.settings" )
|
||||
django.setup( )
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pydash
|
||||
import requests
|
||||
from django.conf import settings
|
||||
|
||||
from requests import post
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class wecom消息推送:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def 上传图片文件( cls ):
|
||||
uri = ''
|
||||
filePath = Path( settings.BASE_DIR ).joinpath( "django_static_directory/media/app.png" )
|
||||
data = requests.post( url=uri, files={ "media": open( file="文件路径", mode="rb", ) } )
|
||||
|
||||
@classmethod
|
||||
def 欢迎语类型IMG( cls ):
|
||||
pass
|
||||
IMG = {
|
||||
"msgtype": "link",
|
||||
"link": {
|
||||
"title": "消息标题",
|
||||
"picurl": "https://example.pic.com/path",
|
||||
"desc": "消息描述",
|
||||
"url": "https://example.link.com/path"
|
||||
}
|
||||
}
|
||||
return IMG
|
||||
|
||||
@classmethod
|
||||
def 欢迎语类型小程序( cls ):
|
||||
pass
|
||||
miniprogram = {
|
||||
"msgtype": "miniprogram", # 附件类型,可选image、link、miniprogram或者video
|
||||
"miniprogram": {
|
||||
"title": "小程序领现金红包", # 是 小程序消息标题,最长为 64 字节
|
||||
"pic_media_id": "3AIBtUrVvuc2qwuSjJSWK9AXyQF_omNpWJqz_4onX_shiGW9U6OsWauNImkKVwJsON0xkcnywtAQJnaXfgmCbIA",
|
||||
# 小程序消息封面的mediaid,封面图建议尺寸为520*416
|
||||
"appid": "wx3acee999f873005a",
|
||||
"page": "/tabPage/Home/Home",
|
||||
},
|
||||
}
|
||||
return miniprogram
|
||||
|
||||
@classmethod
|
||||
def 欢迎语类型LINK( cls ):
|
||||
"""
|
||||
欢迎语类型LINK
|
||||
"""
|
||||
IMG = {
|
||||
"msgtype": "link", # 附件类型,可选image、link、miniprogram或者video
|
||||
"image": {
|
||||
"media_id": "MEDIA_ID", # 图片的media_id,可以通过素材管理接口获得
|
||||
"pic_url": "http://p.qpic.cn/pic_wework/3474110808/7a6344sdadfwehe42060/0" # 图片的链接,仅可使用上传图片接口得到的链接
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def 欢迎语类型VIDEO( cls ):
|
||||
pass
|
||||
VIDEO = {
|
||||
"msgtype": "video",
|
||||
"video": {
|
||||
"media_id": "MEDIA_ID"
|
||||
}
|
||||
}
|
||||
return VIDEO
|
||||
|
||||
@classmethod
|
||||
def 创建企业群发( cls ):
|
||||
pass
|
||||
wecom_token = sixToken.get_wecom_token( )
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_msg_template"
|
||||
json_data = {
|
||||
"chat_type": "single",
|
||||
"external_userid": [
|
||||
"wmj4zANAAAGlCZ2zNRhu1qzEMSBF0a9g", "wmj4zANAAA-lsKNm2U2U-EBCcVGuFGIA",
|
||||
],
|
||||
"attachments": [
|
||||
{
|
||||
"msgtype": "link",
|
||||
"link": {
|
||||
"title": "消息标题",
|
||||
"desc": "消息描述",
|
||||
"url": "https://www.sixgou.com/",
|
||||
"picurl": "https://fastly.picsum.photos/id/82/200/200.jpg?hmac"
|
||||
"=ATNAhTLN2dA0KmTzSE5D9XiPe3GMX8uwxpFlhU7U5OY",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
data = post(
|
||||
url=url, json=json_data, params={
|
||||
"access_token": wecom_token,
|
||||
},
|
||||
)
|
||||
print( data.json( ) )
|
||||
|
||||
@classmethod
|
||||
def 提醒成员群发( cls ):
|
||||
"""
|
||||
提醒成员群发
|
||||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def 停止企业群发( cls ):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def 发送新客户欢迎语( cls, CALLBACK_CODE: str ):
|
||||
""""""
|
||||
wecom_token = sixToken.get_wecom_token( )
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/send_welcome_msg?access_token={wecom_token}"
|
||||
miniprogram = cls.欢迎语类型小程序( )
|
||||
json_data = {
|
||||
"welcome_code": CALLBACK_CODE,
|
||||
"text": {
|
||||
"content": """
|
||||
🎉恭喜!您已成功解锁 给【好友发红包】特权!🔓
|
||||
|
||||
|
||||
🚀更多惊喜权益,开启您的收益之旅:
|
||||
|
||||
|
||||
1️⃣ 双向红包💰:给好友发红包🧧,您也能获得现金奖励💸,分享快乐😊,双倍收获!
|
||||
|
||||
|
||||
2️⃣ 加入赚佣金🤝:成为我们的一员👨👩👧👦,推荐心仪好物❤️,轻松赚取商品推荐分成📈。
|
||||
|
||||
|
||||
3️⃣ 超越赢未来🏆:与我们并肩前行👣,共享海量商品🛍️、优质服务🛠️与庞大流量🌊,共创价值!✨
|
||||
""",
|
||||
},
|
||||
}
|
||||
data = post( url=url, json=json_data )
|
||||
print( "客户欢迎语发送成功了:", data.json( ) )
|
||||
errcode = pydash.get( obj=data.json( ), path="errcode" )
|
||||
if errcode == 41051:
|
||||
pass
|
||||
return data.json( ).get( "errmsg" )
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class wecom获取企业的全部群发记录:
|
||||
"""
|
||||
企业跟第三方应用可通过该接口获取群发给客户的消息和群发到客户群的消息
|
||||
1. 群发助手和客户群群发有以下两种类型
|
||||
2. 企业发表
|
||||
1. 管理员或者业务负责人创建内容,成员确认后,即可发送给客户或者客户群
|
||||
3. 个人发表
|
||||
1. 成员自己创建的内容,可直接发送给客户或客户群
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def 获取群发记录列表( cls ):
|
||||
"""
|
||||
企业和第三方应用可通过此接口获取企业与成员的群发记录。
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
wecom_token = sixToken.get_wecom_token( )
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_groupmsg_list_v2?access_token={wecom_token}"
|
||||
请求数据示例 = {
|
||||
"chat_type": "single",
|
||||
"start_time": 1605171726,
|
||||
"end_time": 1605172726,
|
||||
"creator": "zhangshan",
|
||||
"filter_type": 1,
|
||||
"limit": 50,
|
||||
"cursor": "CURSOR"
|
||||
}
|
||||
data = post( url=url, json=请求数据示例 ).json( )
|
||||
print( data )
|
||||
|
||||
@classmethod
|
||||
def 获取群发成员发送任务列表( cls ):
|
||||
"""
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
wecom_token = sixToken.get_wecom_token( )
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_groupmsg_task?access_token={wecom_token}"
|
||||
请求数据示例 = { "msgid": "msgGCAAAXtWyujaWJHDDGi0mACAAAA", "limit": 50, "cursor": "CURSOR" }
|
||||
data = post( url=url, json=请求数据示例 ).json( )
|
||||
print( data )
|
||||
|
||||
@classmethod
|
||||
def 获取企业群发成员执行结果( cls ):
|
||||
"""
|
||||
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
wecom_token = sixToken.get_wecom_token( )
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_groupmsg_send_result?access_token={wecom_token}"
|
||||
请求数据示例 = {
|
||||
"msgid": "msgGCAAAXtWyujaWJHDDGi0mACAAAA",
|
||||
"userid": "zhangsan ",
|
||||
"limit": 50,
|
||||
"cursor": "CURSOR"
|
||||
}
|
||||
data = post( url=url, json=请求数据示例 ).json( )
|
||||
print( data )
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class wecom入群欢迎语素材管理:
|
||||
"""
|
||||
添加入群欢迎语素材
|
||||
企业可通过此API向企业的入群欢迎语素材库中添加素材。每个企业的入群欢迎语素材库中,最多容纳100个素材。
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def 添加入群欢迎语素材( cls ):
|
||||
wecom_token = sixToken.get_wecom_token( )
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/group_welcome_template/add?access_token={wecom_token}"
|
||||
请求数据示例 = {
|
||||
"text": {
|
||||
"content": "亲爱的%NICKNAME%用户,你好"
|
||||
},
|
||||
"image": {
|
||||
"media_id": "MEDIA_ID",
|
||||
"pic_url": "http://p.qpic.cn/pic_wework/3474110808/7a6344sdadfwehe42060/0"
|
||||
},
|
||||
"link": {
|
||||
"title": "消息标题",
|
||||
"picurl": "https://example.pic.com/path",
|
||||
"desc": "消息描述",
|
||||
"url": "https://example.link.com/path"
|
||||
},
|
||||
"miniprogram": {
|
||||
"title": "消息标题",
|
||||
"pic_media_id": "MEDIA_ID",
|
||||
"appid": "wx8bd80126147dfAAA",
|
||||
"page": "/path/index"
|
||||
},
|
||||
"file": {
|
||||
"media_id": "1Yv-zXfHjSjU-7LH-GwtYqDGS-zz6w22KmWAT5COgP7o"
|
||||
},
|
||||
"video": {
|
||||
"media_id": "MEDIA_ID"
|
||||
},
|
||||
"agentid": 1000014,
|
||||
"notify": 1
|
||||
}
|
||||
data = post( url=url, json=请求数据示例 ).json( )
|
||||
print( data )
|
||||
|
||||
@classmethod
|
||||
def 编辑入群欢迎语素材( cls ):
|
||||
wecom_token = sixToken.get_wecom_token( )
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/group_welcome_template/edit?access_token={wecom_token}"
|
||||
请求数据示例 = {
|
||||
"template_id": "msgXXXXXXX",
|
||||
"text": {
|
||||
"content": "文本消息内容"
|
||||
},
|
||||
"image": {
|
||||
"media_id": "MEDIA_ID",
|
||||
"pic_url": "http://p.qpic.cn/pic_wework/3474110808/7a6344sdadfwehe42060/0"
|
||||
},
|
||||
"link": {
|
||||
"title": "消息标题",
|
||||
"picurl": "https://example.pic.com/path",
|
||||
"desc": "消息描述",
|
||||
"url": "https://example.link.com/path"
|
||||
},
|
||||
"miniprogram": {
|
||||
"title": "消息标题",
|
||||
"pic_media_id": "MEDIA_ID",
|
||||
"appid": "wx8bd80126147df384",
|
||||
"page": "/path/index"
|
||||
},
|
||||
"file": {
|
||||
"media_id": "1Yv-zXfHjSjU-7LH-GwtYqDGS-zz6w22KmWAT5COgP7o"
|
||||
},
|
||||
"video": {
|
||||
"media_id": "MEDIA_ID"
|
||||
},
|
||||
"agentid": 1000014
|
||||
}
|
||||
data = post( url=uri, json=请求数据示例 ).json( )
|
||||
print( data )
|
||||
|
||||
@classmethod
|
||||
def 获取入群欢迎语素材( cls ):
|
||||
wecom_token = sixToken.get_wecom_token( )
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/group_welcome_template/get?access_token={wecom_token}"
|
||||
请求数据示例 = {
|
||||
"template_id": "msgXXXXXXX"
|
||||
}
|
||||
data = post( url=uri, json=请求数据示例 ).json( )
|
||||
print( data )
|
||||
|
||||
@classmethod
|
||||
def 删除入群欢迎语素材( cls ):
|
||||
"""
|
||||
删除入群欢迎语素材
|
||||
"""
|
||||
wecom_token = sixToken.get_wecom_token( )
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/group_welcome_template/del?access_token={wecom_token}"
|
||||
请求数据示例 = {
|
||||
"template_id": "msgXXXXXXX",
|
||||
"agentid": 1000014
|
||||
}
|
||||
data = post( url=uri, json=请求数据示例 ).json( )
|
||||
print( data )
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class uploadTempMedia上传附件资源:
|
||||
|
||||
@classmethod
|
||||
def upload_file( cls ):
|
||||
"""
|
||||
参数 必须 说明
|
||||
access_token 是 调用接口凭证
|
||||
media_type 是 媒体文件类型,分别有图片(image)、视频(video)、普通文件(file)
|
||||
attachment_type 是 附件类型,不同的附件类型用于不同的场景。1:朋友圈;2:商品图册
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
wecom_token = sixToken.get_wecom_token( )
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/media/upload_attachment?access_token={wecom_token}&media_type=image&attachment_type=1"
|
||||
params = { "access_token": wecom_token, "media_type": "image", "attachment_type": "1" }
|
||||
file_path = Path( settings.BASE_DIR ).joinpath( "django_环境/常用类/xiao小程序项目图片/ico运力管理系统.png" )
|
||||
print( file_path )
|
||||
file = { "media": open( file=file_path, mode="rb", ) }
|
||||
|
||||
data = post( url=uri, files=file )
|
||||
media_id = pydash.get( obj=data.json( ), path="media_id" )
|
||||
print( media_id )
|
||||
|
||||
@classmethod
|
||||
def 上传临时素材( cls ):
|
||||
wecom_token = sixToken.get_wecom_token( )
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token={wecom_token}&type=image"
|
||||
# file_path = Path(settings.BASE_DIR).joinpath("django_环境/常用类/xiao小程序项目图片/ico运力管理系统.png")
|
||||
file_path = Path( settings.BASE_DIR ).joinpath( "django_环境//常用类/xiao小程序项目图片/莫大帅分享主图.png" )
|
||||
print( file_path )
|
||||
file = { "media": open( file=file_path, mode="rb", ) }
|
||||
|
||||
data = post( url=uri, files=file )
|
||||
media_id = pydash.get( obj=data.json( ), path="media_id" )
|
||||
print( data.json( ) )
|
||||
print( media_id )
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
uploadTempMedia上传附件资源.上传临时素材( )
|
||||
@@ -0,0 +1,36 @@
|
||||
"""联系我与客户入群方式(加入群聊)工具"""
|
||||
from requests import post
|
||||
|
||||
from wechat_token.token工具包 import sixToken
|
||||
|
||||
|
||||
class wecom客户群_加入群聊_管理:
|
||||
"""注释"""
|
||||
@classmethod
|
||||
def 配置客户群进群方式(cls):
|
||||
"""
|
||||
企业可调用此接口来生成并配置「加入群聊」的二维码或者小程序按钮,客户通过扫描二维码或点击小程序上的按钮,即可加入特定的客户群
|
||||
如果配置的是小程序按钮,需要开发者的小程序接入小程序插件。
|
||||
通过API添加的配置不会在管理端进行展示,每个企业可通过API最多配置50万个「加入群聊」(与「联系我」共用50万的额度)
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
wecom_token = sixToken.get_wecom_token()
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/groupchat/add_join_way?access_token={wecom_token}"
|
||||
请求数据示例 = {
|
||||
"scene": 2, # 是 场景。1 - 群的小程序插件 2 - 群的二维码插件
|
||||
"remark": "aa_remark", # 否 联系方式的备注信息,用于助记,超过30个字符将被截断
|
||||
"auto_create_room": 1, # 否 当群满了后,是否自动新建群。0-否;1-是。 默认为1
|
||||
"room_base_name": "销售客服群", # 否 自动建群的群名前缀,当auto_create_room为1时有效。最长40个utf8字符
|
||||
"room_base_id": 10, # 否 自动建群的群起始序号,当auto_create_room为1时有效
|
||||
# chat_id_list 是 使用该配置的客户群ID列表,最多支持5个。见客户群ID获取方法
|
||||
"chat_id_list": [
|
||||
"wrOgQhDgAAH2Yy-CTZ6POca8mlBEdaaa",
|
||||
"wrOgQhDgAALPUthpRAKvl7mgiQRwAAA"
|
||||
],
|
||||
# 否 企业自定义的state参数,用于区分不同的入群渠道。不超过30个UTF-8字符
|
||||
# 如果有设置此参数,在调用获取客户群详情接口时会返回每个群成员对应的该参数值,详见文末附录2
|
||||
"state": "klsdup3kj3s1"
|
||||
}
|
||||
data = post(uri, json=请求数据示例).json()
|
||||
print(data)
|
||||
@@ -0,0 +1,187 @@
|
||||
"""客户联系我管理"""
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
import django
|
||||
import requests
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from sixGuoDjango.settings import BASE_DIR
|
||||
from wechat_token.token工具包 import sixToken
|
||||
from wechat_数据库.用户数据库.小程序登陆用户 import weiuser
|
||||
from wechat_项目工具包.数据库的工具包.更新用户积分 import miniUser用户角色
|
||||
|
||||
os.environ.setdefault(key="DJANGO_SETTINGS_MODULE", value=f"{BASE_DIR.name}.settings")
|
||||
django.setup()
|
||||
|
||||
import pydash
|
||||
|
||||
from requests import post
|
||||
|
||||
six六果对象xiao, six是否新创建值 = weiuser.objects.update_or_create(unionid="六果",
|
||||
defaults={
|
||||
"user_IP地址": None, "user_昵称": "六果", "user_用户当前的等级": 0, "user_省份": "六果", "user_城市": "六果", "user_区县": "六果",
|
||||
"user_街道信息": "六果", "user_头像": "https://hudie.xiao.band/%E6%9C%AA%E5%91%BD%E5%90%8D.png", "user_绑定企微usrID账号": "六果", "user_角色权限": "六果", "external_userid": "六果",
|
||||
"parent_id": "六果", "mini_openid": "wxf612e825377328f0", "wechat_openid": "六果", "wecom_openid": "wwbe6369dc26a9d83a", "codeConfigId": "六果", "codeRemark": "六果",
|
||||
"codeQrCode": "https://hudie.xiao.band/mini.jpg",
|
||||
"user_企微角色权限": "六果", "user_绑定企微UNIONID": "六果"
|
||||
})
|
||||
|
||||
|
||||
class wecom客户联系_联系我_管理(BaseModel):
|
||||
"""
|
||||
https://developer.work.weixin.qq.com/document/path/92228#%E9%99%84%E5%BD%95
|
||||
"""
|
||||
addres_省: str = Field(..., description="addres_省")
|
||||
address_市: str = Field(..., description="address_市")
|
||||
address_区县: str = Field(..., description="address_区县")
|
||||
|
||||
def 配置客户联系_联系我_方式(self, state企微二维码绑定的唯一值: str, userid_list: list[str]):
|
||||
"""
|
||||
通过API添加的「联系我」不会在管理端进行展示,每个企业可通过API最多配置50万个「联系我」。
|
||||
用户需要妥善存储返回的config_id,config_id丢失可能导致用户无法编辑或删除「联系我」。
|
||||
临时会话模式不占用「联系我」数量,但每日最多添加10万个,并且仅支持单人。
|
||||
临时会话模式的二维码,添加好友完成后该二维码即刻失效。
|
||||
请求方式:POST(HTTPS)
|
||||
请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_contact_way?access_token=ACCESS_TOK
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
|
||||
# wecom_token = "500hJr2pfJIa5vCJoUOK_zJRcKG_4fKdC_pilCgsEds7lQ3VZGlg1FN6iGDKZsMpUqZO4Di9BJfEMxk7V01yz5NYNwaekvby9vw3gZBw40uXr-YqlAVkP6ZRA0Wf6IkHfiYUTDQAk1
|
||||
# -hUANg42srD6f3AfooEi1ZbbmAvXRjsPa17HVgzxLP7NnGTZPbaTOZRU2Cr269GjxZhgx0dVHu1Q"
|
||||
wecom_token = sixToken.get_wecom_token()
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_contact_way?access_token={wecom_token}"
|
||||
state = "oy4Jw6Lyfyj1VvgZFodzq9oqJzHE"
|
||||
请求数据 = {
|
||||
"type": 1, # 联系方式类型,1-单人, 2-多人
|
||||
"scene": 2, # 场景,1-在小程序中联系,2-通过二维码联系
|
||||
"style": 1, # 否 在小程序中联系时使用的控件样式,详见附表
|
||||
"remark": "渠道客户", # 否 联系方式的备注信息,用于助记,不超过30个字符
|
||||
"skip_verify": True, # 否 外部客户添加时是否无需验证,默认为true
|
||||
"state": state企微二维码绑定的唯一值, # 是 区分业务用的;
|
||||
"user": userid_list,
|
||||
}
|
||||
data = requests.post(url=uri, json=请求数据).json()
|
||||
print(data)
|
||||
config_id = pydash.get(obj=data, path="config_id")
|
||||
qr_code = pydash.get(obj=data, path="qr_code")
|
||||
|
||||
def 获取企业已配置的_联系我_列表(self, start时间戳):
|
||||
"""
|
||||
获取六果公司已经创建的全部的添加我联系我二维码 列表数据🆔
|
||||
"""
|
||||
wecom_token = sixToken.get_wecom_token() # list_contact_way
|
||||
print(datetime(year=2026, month=1, day=1).timestamp())
|
||||
start时间戳 = int(datetime(year=2026, month=1, day=1).timestamp())
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/list_contact_way?access_token={wecom_token}"
|
||||
data = post(url=uri, json={"start_time": start时间戳}).json()
|
||||
print(data, "=========")
|
||||
return data
|
||||
|
||||
def 获取企业已配置的_联系我_方式(self, config_id: str):
|
||||
"""
|
||||
获取企业已配置的_联系我_方式
|
||||
:param config_id:
|
||||
:type config_id:
|
||||
"""
|
||||
wecom_token = sixToken.get_wecom_token()
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_contact_way?access_token={wecom_token}"
|
||||
请求数据 = {"config_id": config_id}
|
||||
data = post(url=uri, json=请求数据).json()
|
||||
return data
|
||||
|
||||
def 更新企业已配置的_联系我_方式(self):
|
||||
"""
|
||||
更新企业配置的「联系我」二维码和「联系我」小程序按钮中的信息,如使用人员和备注等。
|
||||
请求方式:POST(HTTPS)
|
||||
请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/update_contact_way?access_token=ACCESS_TOKEN
|
||||
"""
|
||||
wecom_token = sixToken.get_wecom_token()
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/update_contact_way?access_token={wecom_token}"
|
||||
# oy4Jw6A1T3WkKOOhMxcbQTDSM8rs
|
||||
请求示例数据 = {"config_id": "9b18a3ac534e236162a850f77bdd20d0", "remark": "渠道客户", "skip_verify": True, "style": 1, "state": "oy4Jw6A1T3WkKOOhMxcbQTDSM8rs", "type": 2,
|
||||
"user": ["YuanBao1975"],
|
||||
}
|
||||
data = post(url=uri, json=请求示例数据).json()
|
||||
print("更新企业已配置的_联系我_方式", data)
|
||||
|
||||
def 删除企业已配置的_联系我_方式(self, config_id: str):
|
||||
"""
|
||||
删除一个已配置的「联系我」二维码或者「联系我」小程序按钮。
|
||||
请求方式:POST(HTTPS)
|
||||
请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/del_contact_way?access_token=ACCESS_TOKEN
|
||||
"""
|
||||
print(config_id, type(config_id))
|
||||
wecom_token = sixToken.get_wecom_token()
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/del_contact_way?access_token={wecom_token}"
|
||||
请求数据 = {"config_id": config_id}
|
||||
data = post(url=uri, json=请求数据).json()
|
||||
print(data, "==========")
|
||||
return data
|
||||
|
||||
def 结束临时会话(self):
|
||||
"""
|
||||
结束临时会话
|
||||
"""
|
||||
pass
|
||||
|
||||
def 结束语定义(self):
|
||||
"""
|
||||
结束语定义
|
||||
"""
|
||||
pass
|
||||
|
||||
def 最终处理完整的企微二维码信息(self, start时间戳):
|
||||
# 调用获取所有的 原先创建的 列表 得到 config_id
|
||||
config_id_list_data = self.获取企业已配置的_联系我_列表(start时间戳=start时间戳)
|
||||
contact_way = pydash.get(obj=config_id_list_data, path="contact_way")
|
||||
for configID in contact_way:
|
||||
config_id = pydash.get(obj=configID, path="config_id")
|
||||
data_详情数据 = self.获取企业已配置的_联系我_方式(config_id=config_id)
|
||||
qr_code = pydash.get(obj=data_详情数据, path="contact_way.qr_code")
|
||||
state = pydash.get(obj=data_详情数据, path="contact_way.state")
|
||||
userID = pydash.get(obj=data_详情数据, path="contact_way.user.0")
|
||||
remark = pydash.get(obj=data_详情数据, path="contact_way.remark")
|
||||
print(qr_code)
|
||||
wei_obj对象, wei_obj首次创建 = weiuser.objects.get_or_create(unionid=state)
|
||||
if wei_obj首次创建:
|
||||
# 当对象是首次创建时:需要更新一下数据
|
||||
wei_obj对象.user_绑定企微usrID账号 = userID
|
||||
wei_obj对象.codeConfigId = config_id
|
||||
wei_obj对象.codeQrCode = qr_code
|
||||
wei_obj对象.codeRemark = remark
|
||||
wei_obj对象.parent_id = "六果" # 如果是首次创建就需要将当前对象的父元素 默认为 六果公司
|
||||
wei_obj对象.user_企微角色权限 = "企微员工"
|
||||
wei_obj对象.user_省份 = self.addres_省
|
||||
wei_obj对象.user_城市 = self.address_市
|
||||
wei_obj对象.user_区县 = self.address_区县
|
||||
wei_obj对象.user_绑定企微UNIONID = state
|
||||
wei_obj对象.save()
|
||||
print("首次创建,并且数据库更新成功了")
|
||||
else:
|
||||
wei_obj对象.user_绑定企微usrID账号 = userID
|
||||
wei_obj对象.codeConfigId = config_id
|
||||
wei_obj对象.codeQrCode = qr_code
|
||||
wei_obj对象.codeRemark = remark
|
||||
wei_obj对象.user_企微角色权限 = miniUser用户角色().企微员工
|
||||
wei_obj对象.parent_id = wei_obj对象
|
||||
wei_obj对象.user_用户当前的等级 = 1
|
||||
wei_obj对象.user_省份 = self.addres_省
|
||||
wei_obj对象.user_城市 = self.address_市
|
||||
wei_obj对象.user_区县 = self.address_区县
|
||||
wei_obj对象.user_绑定企微UNIONID = state
|
||||
wei_obj对象.save()
|
||||
print("企微员的用户已经存在,并且更新了相关的信息")
|
||||
# 接着 查询所有的 父元素为当前对象的所有用户,再将绑定的二维码信息,更新为当前的对象
|
||||
weiuser.objects.filter(parent=wei_obj对象).update(codeConfigId=wei_obj对象.codeConfigId, user_绑定企微usrID账号=userID,
|
||||
codeQrCode=wei_obj对象.codeQrCode, codeRemark=wei_obj对象.codeRemark)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
#
|
||||
# resData = wecom客户联系_联系我_管理().获取企业已配置的_联系我_方式( config_id="bbc5220b82e614ceeff98fd9f2b04827" )
|
||||
# wecom客户联系_联系我_管理().获取企业已配置的_联系我_列表()
|
||||
start时间戳 = datetime(year=2026, month=1, day=1).timestamp()
|
||||
wecom客户联系_联系我_管理(addres_省="江苏省", address_市="泰州市", address_区县="海陵区").最终处理完整的企微二维码信息(start时间戳=start时间戳) # wecom客户联系_联系我_管理().删除企业已配置的_联系我_方式(config_id="13ee9dcd9a4f7a50ab0f293af90daf3c")
|
||||
@@ -0,0 +1,39 @@
|
||||
"""注释"""
|
||||
|
||||
from requests import get
|
||||
|
||||
from wechat_token.token工具包 import sixToken
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class wecom获取配置了客户联系功能的成员列表:
|
||||
"""
|
||||
获取配置了客户联系功能的成员列表
|
||||
最后更新:2023/12/01
|
||||
企业和第三方服务商可通过此接口获取配置了客户联系功能的成员列表。
|
||||
|
||||
请求方式:GET(HTTPS)
|
||||
请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_follow_user_list?access_token=ACCESS_TOKEN
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def 获取配置了客户联系功能的成员列表( cls ) -> dict:
|
||||
"""
|
||||
获取配置了客户联系功能的成员列表
|
||||
"""
|
||||
wecom_token = sixToken.get_wecom_token( )
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_follow_user_list?access_token={wecom_token}"
|
||||
data = get( url=url ).json( )
|
||||
print( data )
|
||||
return data
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print( wecom获取配置了客户联系功能的成员列表.获取配置了客户联系功能的成员列表( ) )
|
||||
@@ -0,0 +1,80 @@
|
||||
import base64
|
||||
import hashlib
|
||||
|
||||
import jmespath
|
||||
import xmltodict
|
||||
from Crypto.Cipher import AES
|
||||
from django.core.handlers.wsgi import WSGIRequest
|
||||
from rest_framework.request import Request
|
||||
|
||||
|
||||
class wecom设置接收事件服务器通讯录:
|
||||
"""https://work.weixin.qq.com/wework_admin/frame#apps/contactsApi"""
|
||||
# 跟认证目录下的功能是一样的(这里是留一个备份)
|
||||
token = "bVWt1PEUCG" # url验证的token(当前的值为江苏六果科技有限公司)
|
||||
EncodingAESKey = "LS6VsiJA6Vpb3nJlt34vMZFAbuKDJkFuH2KxuEXYuUR" # 配置的解密(当前的值为江苏六果科技有限公司)
|
||||
corp_id = "wwbe6369dc26a9d83a" # 企业微信🆔(当前的值为 江苏六果科技有限公司)
|
||||
corpsecret = "SIRyum7vQh-Bt36AxnqKMX0HIiE-zkYbjYgSat7reZw" # 企业微信 secret(当前的值为 江苏六果科技有限公司)
|
||||
|
||||
# phone_secret = "WNxZZdcU3FRS5msq_kHZaaZOBauBEjJZdQV3VrAxu0c" # 通讯录 secret
|
||||
|
||||
@classmethod
|
||||
def post解密推送消息体(cls, msg_content: bytes):
|
||||
""""""
|
||||
# 从XML中找到Encrypt标签的内容,即加密数据
|
||||
xmldict = xmltodict.parse(msg_content.decode())
|
||||
Encrypt = jmespath.search(data=xmldict, expression="xml.Encrypt", )
|
||||
# 将EncodingAESKey进行Base64解码,并补足填充字符'='
|
||||
AESKey = base64.b64decode(cls.EncodingAESKey + '=')
|
||||
|
||||
# 创建一个AES加密器对象,使用CBC模式和指定的初始化向量(IV)
|
||||
cipher = AES.new(key=AESKey, mode=AES.MODE_CBC, IV=AESKey[:16])
|
||||
# 对加密数据进行Base64解码
|
||||
aes_msg = base64.b64decode(Encrypt)
|
||||
# 使用解密器对象解密数据
|
||||
de_text = cipher.decrypt(plaintext=aes_msg)
|
||||
print(de_text)
|
||||
# 去除解密数据地前16个字节的随机字符串
|
||||
content = de_text[16:]
|
||||
# 解析前4个字节以获取XML内容的实际长度,并将其从网络字节序转换为主机字节序
|
||||
# xml_len = socket.ntohl(struct.unpack("I", content[:4])[0])
|
||||
xml_len = int.from_bytes(content[:4], byteorder='big')
|
||||
# 根据解析出的长度提取实际的XML内容
|
||||
xml_content = content[4: xml_len + 4]
|
||||
# 打印XML内容
|
||||
# 将XML内容解析为字典格式
|
||||
as_dict = xmltodict.parse(xml_input=xml_content)
|
||||
|
||||
# 打印解析后的字典
|
||||
return as_dict
|
||||
|
||||
@classmethod
|
||||
def get请求算法(cls, echostr):
|
||||
""""""
|
||||
AESKey = base64.b64decode(cls.EncodingAESKey + '=')
|
||||
aes_msg = base64.b64decode(echostr)
|
||||
aes = AES.new(key=AESKey, mode=AES.MODE_CBC, iv=aes_msg[:16])
|
||||
de_text = aes.decrypt(plaintext=aes_msg)
|
||||
content = de_text[16:] # 去掉前16随机字节
|
||||
# 取出4字节的msg_len
|
||||
xml_len = int.from_bytes(content[:4], byteorder='big')
|
||||
xml_content = content[4: xml_len + 4]
|
||||
return xml_content
|
||||
|
||||
@classmethod
|
||||
def get解密验证消息(cls, request: WSGIRequest | Request, ):
|
||||
"""接受微信发过来的get请求,拿到参数后解决,并且回复值,结果为验证成功"""
|
||||
GET_OBJ = request.query_params
|
||||
signature = jmespath.search(data=GET_OBJ, expression="msg_signature", )
|
||||
timestamp = jmespath.search(data=GET_OBJ, expression="timestamp", )
|
||||
nonce = jmespath.search(data=GET_OBJ, expression="nonce", )
|
||||
echostr = jmespath.search(data=GET_OBJ, expression="echostr", )
|
||||
# TODO:sorted([token, timestamp, nonce])这是就是对三个参数进行字典排序
|
||||
hashstr = ''.join(sorted([cls.token, timestamp, nonce, echostr, ])) # 这是就是对三个参数进行字典排序
|
||||
sig = hashlib.new(name="sha1", data=hashstr.encode()).hexdigest()
|
||||
if sig == signature:
|
||||
ret = cls.get请求算法(echostr)
|
||||
s = ret.decode().strip()
|
||||
return s
|
||||
else:
|
||||
raise Exception("没有验yghd成功")
|
||||
@@ -0,0 +1,99 @@
|
||||
import jmespath
|
||||
import requests
|
||||
from requests import post
|
||||
|
||||
from wechat_token.token工具包 import sixToken
|
||||
|
||||
|
||||
class 通讯录管理:
|
||||
"""注释"""
|
||||
|
||||
def 成员扩展属性(self):
|
||||
"""
|
||||
|
||||
"""
|
||||
pass
|
||||
"""
|
||||
成员自定义属性需要先在WEB管理端添加,见扩展属性添加方法,否则忽略未知属性的赋值。
|
||||
• 新增/更新:仅通讯录同步助手或第三方通讯录应用可以进行
|
||||
• 查询:代开发自建应用需要管理员授权才返回;第三方仅通讯录应用可获取;对于非第三方创建的成员,第三方通讯录应用也不可获取;上游企业不可获取下游企业成员该字段
|
||||
"""
|
||||
|
||||
def 创建成员(self):
|
||||
"""
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def 读取成员(self):
|
||||
"""
|
||||
|
||||
"""
|
||||
pass
|
||||
"""
|
||||
应用只能获取可见范围内的成员信息,且每种应用获取的字段有所不同,在返回结果说明中会逐个说明。企业通讯录安全特别重要,企业微信将持续升级加固通讯录接口的安全机制,以下是关键的变更点:
|
||||
从2022年6月20号20点开始,除通讯录同步以外的基础应用(如客户联系、微信客服、会话存档、日程等),以及新创建的自建应用与代开发应用,调用该接口时,不再返回以下字段:头像、性别、手机、邮箱、企业邮箱、员工个人二维码、地址,应用需要通过oauth2手工授权的方式获取管理员与员工本人授权的字段。
|
||||
【重要】从2022年8月15日10点开始,“企业管理后台 - 管理工具 - 通讯录同步”的新增IP将不能再调用此接口,企业可通过「获取成员ID列表」和「获取部门ID列表」接口获取userid和部门ID列表。查看调整详情。
|
||||
"""
|
||||
wecom_phone_token = sixToken.get_weom_phone_token()
|
||||
print(wecom_phone_token)
|
||||
wecom_token = sixToken.get_wecom_token()
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/user/get"
|
||||
data = requests.get(url=uri, params={"access_token": wecom_phone_token, "userid": "HuDie_1"})
|
||||
print(data.json())
|
||||
|
||||
def 更新成员(self):
|
||||
"""
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def 删除成员(self):
|
||||
"""
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def 获取成员ID列表(self):
|
||||
"""
|
||||
|
||||
"""
|
||||
pass
|
||||
"""
|
||||
获取企业成员的userid与对应的部门ID列表,预计于2022年8月8号发布。若需要获取其他字段,参见「适配建议」。
|
||||
请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/list_id?access_token=ACCESS_TOKEN
|
||||
"""
|
||||
|
||||
wecom_phone_token = sixToken.get_weom_phone_token()
|
||||
print(wecom_phone_token)
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/user/list_id"
|
||||
data = requests.post(url=uri, params={"access_token": wecom_phone_token})
|
||||
print(data.json())
|
||||
|
||||
def unionid与external_userid的关联(self):
|
||||
"""本接口自建应用是不能调用的,所以,本接口无效,仅限了服务商代开发或"""
|
||||
pass
|
||||
wecom_token = sixToken.get_wecom_token()
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/idconvert/unionid_to_external_userid?access_token={wecom_token}"
|
||||
post_data = {
|
||||
"unionid": "oy4Jw6A1T3WkKOOhMxcbQTDSM8rs",
|
||||
"openid": "o6oV61xoMYAFNpi4RpVjn3BFGB1U",
|
||||
"subject_type": 0
|
||||
}
|
||||
data = post(url=uri, json=post_data).json()
|
||||
print(data)
|
||||
|
||||
@classmethod
|
||||
def userid转openid(cls):
|
||||
wecom_phone_token = sixToken.get_weom_phone_token()
|
||||
uri = f"https://qyapi.weixin.qq.com/cgi-bin/user/convert_to_openid"
|
||||
data = requests.post(url=uri, params={"access_token": wecom_phone_token}, json={"userid": "YuanBao1975"})
|
||||
print(data.json())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
通讯录管理().获取成员ID列表()
|
||||
# 通讯录管理.userid转openid()
|
||||
# 通讯录管理().unionid与external_userid的关联()
|
||||
# 通讯录管理().读取成员()
|
||||
@@ -0,0 +1,92 @@
|
||||
from datetime import datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import orjson
|
||||
import pydash
|
||||
import requests
|
||||
from pydantic import BaseModel
|
||||
|
||||
from wechat_token.token工具包 import sixToken
|
||||
from wechat_微信支付证书.江苏六果科技有限公司支付证书.江苏六果科技有限公司支付参数 import sixpay六果科技公司
|
||||
from wechat_数据库.支付订单数据库.商品订单数据 import order订单
|
||||
|
||||
|
||||
class miniTransaction交易管理服务工具(BaseModel):
|
||||
# demo_必填: int = Field(..., description="描述") # 必传属发生
|
||||
# demo_可选: Optional[int] = None # 可选值
|
||||
# demo_默认值: int = Field(default=100001, description="描述") # 有默认值
|
||||
# demo_联合类型: Literal["xiao", "zizi"] = Field(..., description="描述")
|
||||
def 发货信息录入(self, order支付商家侧订单号, ):
|
||||
pass
|
||||
print("开始调用录入订单信息了")
|
||||
mini_token = sixToken.get_mini_token()
|
||||
order订单对象 = order订单.objects.get(order支付商家侧订单号=order支付商家侧订单号)
|
||||
goods_商品标题: str = pydash.get(obj=pydash.head(order订单对象.order购物车商品数据), path="goods_商品标题")
|
||||
print(goods_商品标题, "00000000000000", order订单对象.order购物车商品数据)
|
||||
print("打印检测 order商家生成的交易订单号", order支付商家侧订单号)
|
||||
print("打印检测 order支付微信官方订单号", order订单对象.order支付微信官方订单号)
|
||||
print("打印检测 order物流公司订单编号", order订单对象.order物流公司订单编号)
|
||||
|
||||
upload_time_上传时间 = datetime.now(tz=ZoneInfo("Asia/Shanghai")).isoformat(timespec="seconds")
|
||||
uri = f"https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info?access_token={mini_token}"
|
||||
录入订单需要的数据 = {
|
||||
"order_key": { # 订单,需要上传物流信息的订单,必传
|
||||
"order_number_type": 1, # 订单单号类型,1-商户号单号 2-微信交易单号,必传
|
||||
"out_trade_no": order支付商家侧订单号,
|
||||
"transaction_id": order订单对象.order支付微信官方订单号, # 原支付交易对应的微信订单号,必传
|
||||
"mchid": sixpay六果科技公司.mchid, # 支付下单商户的商户号,由微信支付生成并下发,必传
|
||||
},
|
||||
"logistics_type": 1, # 物流模式,1-实体物流配送 2-同城配送 3-虚拟商品 4-用户自提,必传
|
||||
"delivery_mode": 1, # 发货模式,1-UNIFIED_DELIVERY(统一发货)2-SPLIT_DELIVERY(分拆发货),必传
|
||||
# "is_all_delivered": True, # 分拆发货模式时必填,标识是否已全部发货完成,非必传
|
||||
"shipping_list": [ # 物流信息列表,支持1~15个物流单,必传
|
||||
{
|
||||
"tracking_no": order订单对象.order物流公司订单编号,
|
||||
"express_company": "EMS", # 物流公司编码,快递公司ID,参见获取运力 id 列表get_delivery_list,物流快递发货时必填, 示例值: DHL 字符字节限制: [1, 128]
|
||||
"item_desc": goods_商品标题, # 商品信息,例如:微信红包抱枕*1个,限120个字以内
|
||||
},
|
||||
],
|
||||
"upload_time": upload_time_上传时间, # 上传时间,用于标识请求的先后顺序,必传
|
||||
"payer": { # 支付者信息,必传
|
||||
"openid": order订单对象.order支付者openid, # 用户在商户appid下的唯一标识,必传
|
||||
},
|
||||
}
|
||||
json_data = orjson.dumps(录入订单需要的数据, option=orjson.OPT_SORT_KEYS, ).decode(encoding="utf-8")
|
||||
postData = requests.post(url=uri, data=json_data)
|
||||
print(postData.json(), "00000000000000000")
|
||||
|
||||
def 查询订单发货状态(self, order支付商家侧订单号):
|
||||
pass
|
||||
mini_token = sixToken.get_mini_token()
|
||||
order订单对象 = order订单.objects.get(order支付商家侧订单号=order支付商家侧订单号)
|
||||
uri = f"https://api.weixin.qq.com/wxa/sec/order/get_order?access_token={mini_token}"
|
||||
查询订单发货状态需要的数据 = {
|
||||
"transaction_id": order订单对象.order支付微信官方订单号, # 原支付交易对应的微信订单号。否
|
||||
"merchant_id": sixpay六果科技公司.mchid, # 否 支付下单商户的商户号,由微信支付生成并下发
|
||||
"merchant_trade_no": order支付商家侧订单号, # 商户系统内部订单号,只能是数字、大小写字母`_-*`且在同一个商户号下唯一。
|
||||
}
|
||||
postData = requests.post(url=uri, json=查询订单发货状态需要的数据)
|
||||
print(postData.json())
|
||||
|
||||
def 确认收货提醒(self, order物流数据, order支付微信官方订单号):
|
||||
pass
|
||||
"""这里的调用的时机为: 目前的业务逻辑是,物流的状态更新用户已签收了 , 我会在此时调用户确认接口 用户会收得到一个 确认收货的提示"""
|
||||
mini_token = sixToken.get_mini_token()
|
||||
uri = f"https://api.weixin.qq.com/wxa/sec/order/notify_confirm_receive?access_token={mini_token}"
|
||||
# 快递签收时间设定为 2026-07-24 04:11:24
|
||||
action_time_签收时间戳 = pydash.get(obj=pydash.head(order物流数据), path="action_time")
|
||||
data = {
|
||||
"transaction_id": order支付微信官方订单号, # 原支付交易对应的微信订单号,非必传
|
||||
"received_time": action_time_签收时间戳, # 快递签收时间,时间戳形式,必传
|
||||
}
|
||||
postData = requests.post(url=uri, json=data)
|
||||
# 当请求发送后, 因为只能发送一次,所以需要直接更新状态,
|
||||
更新对象 = order订单.objects.get(order支付微信官方订单号=order支付微信官方订单号)
|
||||
更新对象.order物流状态 = "待用户确认收货"
|
||||
更新对象.save()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
# miniTransaction交易管理服务工具().查询订单发货状态(order支付商家侧订单号="OAT01KYDFM1E8NS7DXRX18RRYVR52")
|
||||
miniTransaction交易管理服务工具().发货信息录入(order支付商家侧订单号="OAT01KYNF9MXZ0CZ8A7Q0R3TBS55E")
|
||||
@@ -0,0 +1,222 @@
|
||||
"""
|
||||
Author: xiao 792282@qq.com
|
||||
Date: 2025-08-10 11:36:05
|
||||
LastEditors: xiao 792282@qq.com
|
||||
LastEditTime: 2025-08-17 13:38:40
|
||||
FilePath: django_小程序/同城配送工具/同城配送.py
|
||||
Description: 这是默认设置,可以在设置》工具》File Description中进行配置
|
||||
"""
|
||||
import os
|
||||
|
||||
import django
|
||||
|
||||
from sixGuoDjango.settings import BASE_DIR
|
||||
from wechat_token.token工具包 import sixToken
|
||||
|
||||
os.environ.setdefault(key="DJANGO_SETTINGS_MODULE", value=f"{BASE_DIR.name}.settings")
|
||||
django.setup()
|
||||
|
||||
import json
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
import pydash
|
||||
from django.conf import settings
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
import base64
|
||||
from requests import Response, post
|
||||
import orjson
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Hash import SHA256
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto import Random
|
||||
from Crypto.Signature import pss
|
||||
|
||||
public_key = Path(settings.BASE_DIR).joinpath("wechat_微信支付证书/江苏六果科技有限公司支付证书/public_key.pem").read_text()
|
||||
# public_key = Path("../../../wechat_微信支付证书/江苏六果科技有限公司支付证书/public_key.pem").resolve().read_text()
|
||||
private_key = Path(settings.BASE_DIR).joinpath("wechat_微信支付证书/江苏六果科技有限公司支付证书/private_key.pem").read_text()
|
||||
# private_key = Path("../../../wechat_微信支付证书/江苏六果科技有限公司支付证书/private_key.pem").resolve().read_text()
|
||||
平台证书文本 = Path(settings.BASE_DIR).joinpath("wechat_微信支付证书/江苏六果科技有限公司支付证书/开放平台证书.pem").read_text()
|
||||
|
||||
|
||||
# 平台证书文本 = Path("../../../wechat_微信支付证书/江苏六果科技有限公司支付证书/开放平台证书.pem").resolve().read_text()
|
||||
|
||||
|
||||
class postData请求体类型(BaseModel):
|
||||
iv: str
|
||||
data: str
|
||||
authtag: str
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class six同城加解密工具:
|
||||
"""同城配送类加签,解密"""
|
||||
# add数据: bytes
|
||||
uri_urls: str
|
||||
data_body: dict
|
||||
_timestamp: int = field(init=False)
|
||||
uri_path: str = field(init=False)
|
||||
mini_token: str = field(init=False)
|
||||
add_GCM使用的认证数据_ADD数据: bytes = field(init=False)
|
||||
postData加密后的数据: postData请求体类型 = field(init=False)
|
||||
heardes_请求头数据: dict = field(init=False)
|
||||
aes_key对称密钥 = base64.b64decode("CV/RZev/1A1eg7TOgzvEOy5/to2aM8SE6Ab/R9E3BgI=")
|
||||
aes_str对称密钥 = "CV/RZev/1A1eg7TOgzvEOy5/to2aM8SE6Ab/R9E3BgI="
|
||||
aes对称密钥编号 = "b02be3855ff9b990b2183505f165317e"
|
||||
rsa非对称密钥编号 = "442e1b521422ec70844a3272e5c857dd"
|
||||
api开放平台证书编号 = "5330e9294308d338a5e145fd03d0ea6a"
|
||||
平台证书对象 = 平台证书文本
|
||||
rsa_private = private_key
|
||||
rsa_public = public_key
|
||||
mini_appid = "wxf612e825377328f0"
|
||||
mini_AppSecret = "8691f774e3c0c5a876b21a3e84109ef0"
|
||||
|
||||
def __post_init__(self):
|
||||
self.mini_token = sixToken.get_mini_token()
|
||||
self._timestamp = int(time.time())
|
||||
|
||||
# self.本地解密( )
|
||||
|
||||
def 加密(self, ):
|
||||
"""
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
iv_random_bytes = Random.get_random_bytes(12)
|
||||
iv_base64_12 = base64.b64encode(iv_random_bytes).decode()
|
||||
random_bytes_n = Random.get_random_bytes(16)
|
||||
_n = base64.b64encode(random_bytes_n).decode() # 随机字符串,_n的值 推荐使用16-32 字节非固定长度随机base64字符串
|
||||
_appid = self.mini_appid # 当前小程序的Appid
|
||||
self.add_GCM使用的认证数据_ADD数据 = (f"{self.uri_urls}|{self.mini_appid}|{str(self._timestamp).strip()}|"
|
||||
f"{self.aes对称密钥编号}").encode(encoding="utf-8")
|
||||
data明文 = {
|
||||
"_n": _n, "_appid": self.mini_appid, "_timestamp": self._timestamp,
|
||||
}
|
||||
data明文格式 = dict(data明文, **self.data_body)
|
||||
data明文格式_bytes = orjson.dumps(data明文格式).decode()
|
||||
cliper = AES.new(key=self.aes_key对称密钥, mode=AES.MODE_GCM, nonce=iv_random_bytes)
|
||||
"""
|
||||
AAD
|
||||
1:攻击者无法修改 AAD 而不被发现(解密时会验证失败)。
|
||||
2:确保加密数据与特定请求关联(如 API 路径、AppID 等)。
|
||||
3: 不加密但受保护:AAD 本身不加密,但它的完整性会被 MAC 验证。
|
||||
"""
|
||||
cliper.update(assoc_data=self.add_GCM使用的认证数据_ADD数据) #
|
||||
real_ciphertext, real_authTag = cliper.encrypt_and_digest(data明文格式_bytes.encode())
|
||||
data_base64 = base64.b64encode(real_ciphertext).decode()
|
||||
authtag_base64 = base64.b64encode(real_authTag).decode()
|
||||
self.postData加密后的数据 = postData请求体类型(**{"iv": iv_base64_12, "data": data_base64, "authtag": authtag_base64, })
|
||||
self.本地解密() # 测试调用一下本地解密,是否有效;
|
||||
print("这里会报错的吗")
|
||||
|
||||
def 本地解密(self, ):
|
||||
"""
|
||||
解密功能
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
# 将base64字符串解码;
|
||||
iv_bytes = base64.b64decode(self.postData加密后的数据.iv) # 将base64字符串解码;
|
||||
ciphertext = base64.b64decode(self.postData加密后的数据.data) # 将base64字符串解码;
|
||||
cipher = AES.new(self.aes_key对称密钥, AES.MODE_GCM, nonce=iv_bytes)
|
||||
cipher.update(self.add_GCM使用的认证数据_ADD数据)
|
||||
try:
|
||||
padded_data = cipher.decrypt(ciphertext)
|
||||
return
|
||||
except ValueError:
|
||||
raise ValueError("解密出错了")
|
||||
|
||||
def 对加密数据进行签名(self, ):
|
||||
"""
|
||||
对返回的加密数据进行解密;
|
||||
"""
|
||||
# 转换为紧凑 JSON 格式
|
||||
postdata = json.dumps(self.postData加密后的数据.model_dump(), ensure_ascii=False, )
|
||||
# 构造签名串
|
||||
sign_str = f"{self.uri_urls}\n{self.mini_appid}\n{self._timestamp}\n{postdata}"
|
||||
# RSA 签名
|
||||
private_key_obj = RSA.import_key(self.rsa_private)
|
||||
h = SHA256.new(sign_str.encode())
|
||||
|
||||
# ✅ 改为 PSS 签名
|
||||
signature = pss.new(private_key_obj).sign(h)
|
||||
sig = base64.b64encode(signature).decode()
|
||||
|
||||
self.heardes_请求头数据 = {
|
||||
"Wechatmp-Appid": self.mini_appid,
|
||||
"Wechatmp-TimeStamp": str(self._timestamp),
|
||||
"Wechatmp-Signature": sig,
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
}
|
||||
|
||||
def 验证签名串(self, respose: Response):
|
||||
""""""
|
||||
# 获取原始响应文本(避免任何自动转换,我们使用response.content然后手动解码为utf-8)
|
||||
Wechatmp_TimeStamp = pydash.get(obj=respose.headers, path="Wechatmp-TimeStamp", default=None)
|
||||
Wechatmp_Appid = pydash.get(obj=respose.headers, path="Wechatmp-Appid", default=None)
|
||||
Wechatmp_Signature = pydash.get(obj=respose.headers, path="Wechatmp-Signature", default=None)
|
||||
# 2. 获取原始响应体(非常重要)
|
||||
raw_body = respose.text.strip() # 去除首尾空白,确保无额外\n
|
||||
创建拼接字答串 = f"{self.uri_urls}\n{Wechatmp_Appid}\n{str(Wechatmp_TimeStamp)}\n{raw_body}"
|
||||
signature = base64.b64decode(Wechatmp_Signature)
|
||||
msg_hash = SHA256.new(创建拼接字答串.encode(encoding="utf-8"))
|
||||
public_obj = RSA.import_key(self.平台证书对象)
|
||||
# 创建PSS签名验证器
|
||||
try:
|
||||
pss.new(rsa_key=public_obj).verify(msg_hash=msg_hash, signature=signature) # type: ignore
|
||||
print("验证成功了")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 签名验证失败: {str(e)}")
|
||||
raise e
|
||||
|
||||
def 解签(self, respose: Response):
|
||||
Wechatmp_TimeStamp = pydash.get(obj=respose.headers, path="Wechatmp-TimeStamp", default=None)
|
||||
Wechatmp_Appid = pydash.get(obj=respose.headers, path="Wechatmp-Appid", default=None)
|
||||
# Wechatmp_Signature 平台证书签名数据,使用base64编码
|
||||
self.验证签名串(respose=respose)
|
||||
# ✅ 强制重新构造 AAD,确保与加密端一致
|
||||
add_data = f"{self.uri_urls}|{Wechatmp_Appid}|{Wechatmp_TimeStamp}|{self.aes对称密钥编号}"
|
||||
iv = pydash.get(obj=respose.json(), path="iv", default=None)
|
||||
authtag = pydash.get(obj=respose.json(), path="authtag", default=None)
|
||||
data = pydash.get(obj=respose.json(), path="data", default=None)
|
||||
iv_bytes = base64.b64decode(iv)
|
||||
ciphertext = base64.b64decode(data)
|
||||
cipher = AES.new(self.aes_key对称密钥, AES.MODE_GCM, nonce=iv_bytes)
|
||||
cipher.update(add_data.encode()) # ✅ 使用新构造的 AAD
|
||||
try:
|
||||
padded_data = cipher.decrypt(ciphertext) # 只能调用decrypt这个方法()
|
||||
deciphertext = padded_data.decode()
|
||||
return deciphertext
|
||||
except ValueError as e:
|
||||
raise ValueError("解密出错了,请检查 AAD、IV、密文或密钥一致性") from e
|
||||
|
||||
def call(self):
|
||||
""""""
|
||||
self.加密()
|
||||
self.对加密数据进行签名()
|
||||
data = post(url=self.uri_urls, params={"access_token": self.mini_token, }, headers=self.heardes_请求头数据, json=self.postData加密后的数据.model_dump())
|
||||
print(data.json(), "请求出错了吗")
|
||||
return self.解签(respose=data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# six同城加解密工具.对加密数据进行签名( urlpath="https://api.weixin.qq.com/wxa/getuserriskrank", data_body={ } ) # #
|
||||
# six = six同城加解密工具(uri_urls="https://api.weixin.qq.com/cgi-bin/express/intracity/querystore",
|
||||
# data_body={}).call()
|
||||
# print(six)
|
||||
查询运费请求数据 = {
|
||||
"wx_store_id": "4000000000016308013",
|
||||
"user_lng": "119.893083",
|
||||
"user_lat": "32.474616",
|
||||
"user_address": "江苏省泰州市海陵区城西街道任景路U牌美容店",
|
||||
"user_name": "元宝",
|
||||
"user_phone": "13372551360",
|
||||
"cargo": {"cargo_name": "测试商品", "cargo_type": 3, "cargo_num": 7, "cargo_price": 5000, "cargo_weight": 2500}
|
||||
}
|
||||
res = six同城加解密工具(uri_urls="https://api.weixin.qq.com/cgi-bin/express/intracity/preaddorder", data_body=查询运费请求数据).call()
|
||||
print(res)
|
||||
@@ -0,0 +1,73 @@
|
||||
"""
|
||||
Author: xiao 792282@qq.com
|
||||
Date: 2025-08-10 11:36:05
|
||||
LastEditors: xiao 792282@qq.com
|
||||
LastEditTime: 2025-08-17 13:38:39
|
||||
FilePath: django_小程序/同城配送工具/小程序用户授权数据.py
|
||||
Description: 这是默认设置,可以在设置》工具》File Description中进行配置
|
||||
"""
|
||||
from wechat_项目工具包.微信支付工具包.微信支付类 import paymentCategory
|
||||
|
||||
|
||||
|
||||
"""功能模块"""
|
||||
import jmespath
|
||||
from requests import post, get
|
||||
|
||||
|
||||
class wei小程序服务端工具:
|
||||
"""微信登陆"""
|
||||
|
||||
@classmethod
|
||||
def 获取OPENID(cls, secret: str, js_code: str, appid: str, ) -> dict:
|
||||
"""
|
||||
@param secret: 小程序 appSecret
|
||||
@type secret: string
|
||||
@param js_code: 登录时获取的 code,可通过wx.login获取
|
||||
@type js_code: string
|
||||
@param appid: 小程序的app🆔
|
||||
@type appid: string
|
||||
@return:{"session_key":"etC7EOml89Pd8XIhTFyAOA==","expires_in":7200,"openid":"oNTAd0fm1GQAdohfbvywhGstsIPc","unionid":"ojWkM1MnLrdty0S3Vks2pYorp4MY"}
|
||||
@rtype: dict
|
||||
"""
|
||||
url = "https://api.weixin.qq.com/sns/jscode2session"
|
||||
data = {
|
||||
"appid": appid,
|
||||
"secret": secret,
|
||||
"js_code": js_code,
|
||||
"grant_type": "authorization_code"
|
||||
}
|
||||
res_data = get(url=url, params=data)
|
||||
res = res_data.json()
|
||||
errcode = jmespath.search(data=res, expression="errcode", )
|
||||
if res_data.status_code == 200:
|
||||
if errcode is None:
|
||||
return res
|
||||
else:
|
||||
raise Exception(f"登陆错误码:{res}")
|
||||
raise Exception("请求login登陆时出错了")
|
||||
|
||||
@classmethod
|
||||
def 获取手机号(cls, url, code: str):
|
||||
"""
|
||||
@param url:
|
||||
@type url:
|
||||
@param code:
|
||||
@type code:
|
||||
@return: {'session_key': 'etC7EOml89Pd8XIhTFyAOA==', 'expires_in': 7200, 'openid': 'oNTAd0fm1GQAdohfbvywhGstsIPc', 'unionid': 'ojWkM1MnLrdty0S3Vks2pYorp4MY'}
|
||||
@rtype:
|
||||
"""
|
||||
data = {
|
||||
"code": code
|
||||
}
|
||||
res_data = post(url=url, json=data)
|
||||
res = res_data.json() # type: dict
|
||||
return res
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
res = wei小程序服务端工具.获取OPENID(
|
||||
js_code="0e1sYCkl2bCmhg4DG2ll2kheh10sYCkn", appid=paymentCategory.appid,
|
||||
secret=paymentCategory.appsecret,
|
||||
)
|
||||
@@ -0,0 +1,138 @@
|
||||
import io
|
||||
import os
|
||||
|
||||
import django
|
||||
import pydash
|
||||
from django.core.files.storage import default_storage
|
||||
|
||||
from wechat_token.token工具包 import sixToken
|
||||
|
||||
|
||||
|
||||
django.setup()
|
||||
from requests import post
|
||||
|
||||
|
||||
class 小程序码与小程序链接:
|
||||
"""获取所有的小程序码的业务接口"""
|
||||
|
||||
def 小程序码_getQRCode(self):
|
||||
"""
|
||||
该接口用于获取小程序码,适用于需要的码数量较少的业务场景。通过该接口生成的小程序码,永久有效,有数量限制,详见获取小程序码。
|
||||
"""
|
||||
pass
|
||||
mini_token = sixToken.get_mini_token()
|
||||
post_url = f"https://api.weixin.qq.com/wxa/getwxacode?access_token={mini_token}"
|
||||
post_data = {
|
||||
"path": "tabPage/Home/Home?xiao=zizi",
|
||||
# 扫码进入的小程序页面路径,最大长度 1024 个字符,不能为空,scancode_time为系统保留参数,不允许配置;对于小游戏,可以只传入 query 部分,来实现传参效果,如:传入 "?foo=bar",即可在 wx.getLaunchOptionsSync 接口中的 query 参数获取到 {foo:"bar"}。
|
||||
"is_hyaline": True, # 默认值false;是否需要透明底色,为 true 时,生成透明底色的小程序码
|
||||
"env_version": "trial", # 要打开的小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop"。默认是正式版。
|
||||
}
|
||||
data = post(url=post_url, json=post_data)
|
||||
default_storage.save(name="uploads/xiao.png", content=io.BytesIO(initial_bytes=data.content)) # 作用:是可以自动将图片保存在指定的media目录下的 uploads目录下的图片名 xiao.png
|
||||
|
||||
def 小程序码_getUnlimitedQRCode(self):
|
||||
"""
|
||||
该接口用于获取小程序码,适用于需要的码数量极多的业务场景。通过该接口生成的小程序码,永久有效,数量暂无限制。 更多用法详见 获取小程序码。
|
||||
"""
|
||||
pass
|
||||
mini_token = sixToken.get_mini_token()
|
||||
post_url = f"https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={mini_token}"
|
||||
post_data = {
|
||||
"path": "tabPage/Home/Home?zizi=xiaozizi", # 非必填 默认是主页,页面 page,例如 pages/index/index,根路径前不要填加 /,不能携带参数(参数请放在scene字段里),如果不填写这个字段,默认跳主页面。scancode_time为系统保留参数,不允许配置
|
||||
"scene": "abc", # 必填 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符
|
||||
"is_hyaline": True, # 默认值false;是否需要透明底色,为 true 时,生成透明底色的小程序码
|
||||
"env_version": "trial", # 要打开的小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop"。默认是正式版。
|
||||
}
|
||||
data = post(url=post_url, json=post_data)
|
||||
print(data.status_code)
|
||||
default_storage.save(name="uploads/getUnlimitedQRCode.png", content=io.BytesIO(initial_bytes=data.content)) # 作用:是可以自动将图片保存在指定的media目录下的 uploads目录下的图片名 xiao.png
|
||||
|
||||
def 小程序码_createQRCode(self):
|
||||
"""
|
||||
该接口用于获取小程序二维码,适用于需要的码数量极多的业务场景。通过该接口生成的小程序码,永久有效,数量暂无限制。 更多用法详见 获取小程序码。
|
||||
"""
|
||||
pass
|
||||
mini_token = sixToken.get_mini_token()
|
||||
post_url = f"https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token={mini_token}"
|
||||
post_data = {
|
||||
"path": "tabPage/Home/Home?foo=bar", # 非必填 默认是主页,页面 page,例如 pages/index/index,根路径前不要填加 /,不能携带参数(参数请放在scene字段里),如果不填写这个字段,默认跳主页面。scancode_time为系统保留参数,不允许配置
|
||||
"scene": "abc", # 必填 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符
|
||||
"is_hyaline": True, # 默认值false;是否需要透明底色,为 true 时,生成透明底色的小程序码
|
||||
"env_version": "trial", # 要打开的小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop"。默认是正式版。
|
||||
}
|
||||
data = post(url=post_url, json=post_data)
|
||||
print(data.status_code)
|
||||
default_storage.save(name="uploads/createQRCode.png", content=io.BytesIO(initial_bytes=data.content)) # 作用:是可以自动将图片保存在指定的media目录下的 uploads目录下的图片名 xiao.png
|
||||
|
||||
def urlScheme_获取加密Scheme(self):
|
||||
mini_token = sixToken.get_mini_token()
|
||||
post_url = f"https://api.weixin.qq.com/wxa/generatescheme?access_token={mini_token}"
|
||||
post_data = {
|
||||
"jump_wxa":
|
||||
{
|
||||
"path": "tabPage/Home/Home", # 通过 scheme 码进入的小程序页面路径,必须是已经发布的小程序存在的页面,不可携带 query。path 为空时会跳转小程序主页。
|
||||
"query": "foo=bar", # 通过 scheme 码进入小程序时的 query,最大1024个字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~%`
|
||||
"env_version": "trial", # 默认值"release"。要打开的小程序版本。正式版为"release",体验版为"trial",开发版为"develop",仅在微信外打开时生效
|
||||
},
|
||||
"is_expire": True,
|
||||
"expire_type": 1, # number 否 默认值0,到期失效的 scheme 码失效类型,失效时间:0,失效间隔天数:1
|
||||
"expire_interval": 1, # number 否到期失效的 scheme 码的失效间隔天数。生成的到期失效 scheme 码在该间隔时间到达前有效。最长间隔天数为30天。is_expire 为 true 且 expire_type 为 1 时必填
|
||||
}
|
||||
|
||||
data = post(url=post_url, json=post_data)
|
||||
print(data.status_code)
|
||||
print(data.json())
|
||||
openlink = pydash.get(obj=data.json(), path="openlink")
|
||||
print(openlink)
|
||||
# default_storage.save(name="uploads/urlScheme.png", content=io.BytesIO(initial_bytes=data.content)) # 作用:是可以自动将图片保存在指定的media目录下的 uploads目录下的图片名 xiao.png
|
||||
|
||||
def shortLink(self):
|
||||
"""
|
||||
获取小程序 Short Link,适用于微信内拉起小程序的业务场景。目前只开放给电商类目(具体包含以下一级类目:电商平台、商家自营、跨境电商)。通过该接口,可以选择生成到期失效和永久有效的小程序短链,详情见获取 Short Link。
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
mini_token = sixToken.get_mini_token()
|
||||
post_url = f"https://api.weixin.qq.com/wxa/genwxashortlink?access_token={mini_token}"
|
||||
post_data = {
|
||||
"page_url": "tabPage/Home/Home?q=1", # 通过 Short Link 进入的小程序页面路径,必须是已经发布的小程序存在的页面,可携带 query,最大1024个字符
|
||||
"page_title": "Homework title", # string 否 页面标题,不能包含违法信息,超过20字符会用... 截断代替
|
||||
"is_permanent": True, # boolean 否 默认值false。生成的 Short Link 类型,短期有效:false,永久有效:true
|
||||
}
|
||||
|
||||
data = post(url=post_url, json=post_data)
|
||||
print(data.status_code)
|
||||
print(data.json())
|
||||
link = pydash.get(obj=data.json(), path="link")
|
||||
print(link)
|
||||
|
||||
def urlLink(self):
|
||||
"""
|
||||
获取小程序 URL Link,适用于短信、邮件、网页、微信内等拉起小程序的业务场景。
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
mini_token = sixToken.get_mini_token()
|
||||
post_url = f"https://api.weixin.qq.com/wxa/generate_urllink?access_token={mini_token}"
|
||||
post_data = {
|
||||
"path": "tabPage/Home/Home", # 通过 URL Link 进入的小程序页面路径,必须是已经发布的小程序存在的页面,不可携带 query 。path 为空时会跳转小程序主页
|
||||
"query": "aaa=bbb", # 通过 URL Link 进入小程序时的query,最大1024个字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~%
|
||||
}
|
||||
|
||||
data = post(url=post_url, json=post_data)
|
||||
print(data.status_code)
|
||||
print(data.json())
|
||||
url_link = pydash.get(obj=data.json(), path="url_link")
|
||||
print(url_link)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
# 小程序码与小程序链接().小程序码_getQRCode() # 生成的是小程序码
|
||||
# 小程序码与小程序链接().小程序码_getUnlimitedQRCode() #
|
||||
# 小程序码与小程序链接().小程序码_createQRCode() # 生成的是二维码
|
||||
小程序码与小程序链接().urlScheme_获取加密Scheme() # weixin://dl/business/?t=vQAOKCN3Shu 生成的链接 必须要有正式版本才可以
|
||||
小程序码与小程序链接().shortLink() # 小程序://元宝零售/Homeworktitle/Lsr22VT1TDJwlyj 生成的是链接 必须要有正式版本才可以 目前只开放给电商类目(具体包含以下一级类目:电商平台、商家自营、跨境电商)
|
||||
小程序码与小程序链接().urlLink() # 生成的是链接 必须要有正式版本才可以 https://wxaurl.cn/nJAFXSaIgym
|
||||
@@ -0,0 +1,89 @@
|
||||
"""
|
||||
Author: xiao 792282@qq.com
|
||||
Date: 2025-08-10 11:36:05
|
||||
LastEditors: xiao 792282@qq.com
|
||||
LastEditTime: 2025-08-17 13:38:40
|
||||
FilePath: django_小程序/同城配送工具/小程序订单管理工具.py
|
||||
Description: 这是默认设置,可以在设置》工具》File Description中进行配置
|
||||
"""
|
||||
from wechat_token.token工具包 import sixToken
|
||||
|
||||
|
||||
|
||||
"""小程序订单管理工具"""
|
||||
import os
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import orjson
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
class six订单管理:
|
||||
"""录入订单接口的类"""
|
||||
|
||||
@classmethod
|
||||
def order发货信息录入接口(cls, iso_format_time_inner: str, transaction_id: str, openid: str):
|
||||
"""
|
||||
调用 录入订单接口,自动录入订单
|
||||
"""
|
||||
mini_token = sixToken.get_mini_token()
|
||||
uri = f"https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info?access_token={mini_token}"
|
||||
# 订单数据 = {
|
||||
# "order_key": { # 订单,需要上传物流信息的订单
|
||||
# "order_number_type": 2, # 是 订单单号类型,用于确认需要上传详情的订单。枚举值1,使用下单商户号和商户侧单号;枚举值2,使用微信支付单号。
|
||||
# "transaction_id": "4200002639202506141300476684" # 否 原支付交易对应的微信订单号
|
||||
# },
|
||||
# "delivery_mode": 2, # 发货模式,发货模式枚举值:1、UNIFIED_DELIVERY(统一发货)2、SPLIT_DELIVERY(分拆发货) 示例值: UNIFIED_DELIVERY
|
||||
# "logistics_type": 2, # 是 物流模式,发货方式枚举值:1、实体物流配送采用快递公司进行实体物流配送形式 2、同城配送 3、虚拟商品,虚拟商品,例如话费充值,点卡等,无实体配送形式 4、用户自提
|
||||
# "shipping_list": [ # 是 物流信息列表,发货物流单列表,支持统一发货(单个物流单)和分拆发货(多个物流单)两种模式,多重性: [1, 10]
|
||||
# {
|
||||
# # "tracking_no": "fake-trackingno-2022121419042711",
|
||||
# # 否 tracking_no 物流单号,物流快递发货时必填,示例值: 323244567777 字符字节限制: [1, 128]
|
||||
# # "express_company": "STO",
|
||||
# # 否 物流公司编码,快递公司ID,参见「获取运力 id 列表get_delivery_list」,物流快递发货时必填, 示例值: DHL 字符字节限制: [1, 128]
|
||||
# "item_desc": "卷纸", # 是 商品信息,例如:微信红包抱枕*1个,限120个字以内
|
||||
# # "contact": { # contact否 联系方式,当发货的物流公司为顺丰时,联系方式为必填,收件人或寄件人联系方式二选一
|
||||
# # "consignor_contact": "+86-177****1234" # 否 寄件人联系方式,寄件人联系方式,采用掩码传输,最后4位数字不能打掩码 示例值:0, ****123-
|
||||
# # }
|
||||
# }
|
||||
# ],
|
||||
# "upload_time": iso_format_time, # 是 上传时间,用于标识请求的先后顺序 示例值: `2022-12-15T13:29:35.120+08:00`
|
||||
# "payer": { # payer 支付者,支付者信息
|
||||
# "openid": "ohQkD7A-N93kljcFmkCyG6e_D9vg"
|
||||
# # 用户标识,用户在小程序appid下的唯一标识。 下单前需获取到用户的Openid 示例值: oUpF8uMuAJO_M2pxb1Q9zNjWeS6o 字符字节限制: [1, 128]
|
||||
# }
|
||||
# }
|
||||
订单数据 = {
|
||||
"order_key": {
|
||||
"order_number_type": 2,
|
||||
"transaction_id": transaction_id # 使用真实的微信支付单号
|
||||
},
|
||||
"delivery_mode": 1,
|
||||
"logistics_type": 2,
|
||||
# "is_all_delivered": True,
|
||||
"shipping_list": [
|
||||
{
|
||||
"item_desc": "卷纸", # 确保中文字符被正确处理
|
||||
}
|
||||
],
|
||||
"upload_time": iso_format_time_inner, # 修正时间格式
|
||||
"payer": {
|
||||
"openid": openid # 真实的用户openid
|
||||
}
|
||||
}
|
||||
|
||||
data = requests.post(url=uri, data=orjson.dumps(订单数据).decode(), )
|
||||
print(data.json())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
rfc_3339_time = datetime.now(tz=ZoneInfo("Asia/Shanghai")).isoformat(timespec="seconds")
|
||||
print(rfc_3339_time)
|
||||
# 如果报错:则需要查看一下,是不是手机真实支付的,如果是开发工具并且是扫码支付的则不会出现这个订单;
|
||||
six订单管理.order发货信息录入接口(iso_format_time_inner=rfc_3339_time,
|
||||
transaction_id="4200002720202508164227110344",
|
||||
openid="ohQkD7A-N93kljcFmkCyG6e_D9vg")
|
||||
@@ -0,0 +1,76 @@
|
||||
"""
|
||||
Author: xiao 792282@qq.com
|
||||
Date: 2025-08-10 11:36:05
|
||||
LastEditors: xiao 792282@qq.com
|
||||
LastEditTime: 2025-08-17 13:38:39
|
||||
FilePath: django_小程序/同城配送工具/小程序用户授权数据.py
|
||||
Description: 这是默认设置,可以在设置》工具》File Description中进行配置
|
||||
"""
|
||||
|
||||
"""功能模块"""
|
||||
import jmespath
|
||||
from requests import post, get
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class wei小程序服务端工具:
|
||||
"""微信登陆"""
|
||||
|
||||
@classmethod
|
||||
def 获取OPENID( cls, secret: str, js_code: str, appid: str, ) -> dict:
|
||||
"""
|
||||
@param secret: 小程序 appSecret
|
||||
@type secret: string
|
||||
@param js_code: 登录时获取的 code,可通过wx.login获取
|
||||
@type js_code: string
|
||||
@param appid: 小程序的app🆔
|
||||
@type appid: string
|
||||
@return:{"session_key":"etC7EOml89Pd8XIhTFyAOA==","expires_in":7200,"openid":"oNTAd0fm1GQAdohfbvywhGstsIPc","unionid":"ojWkM1MnLrdty0S3Vks2pYorp4MY"}
|
||||
@rtype: dict
|
||||
"""
|
||||
url = "https://api.weixin.qq.com/sns/jscode2session"
|
||||
data = {
|
||||
"appid": appid,
|
||||
"secret": secret,
|
||||
"js_code": js_code,
|
||||
"grant_type": "authorization_code"
|
||||
}
|
||||
res_data = get( url=url, params=data )
|
||||
res = res_data.json( )
|
||||
errcode = jmespath.search( data=res, expression="errcode", )
|
||||
if res_data.status_code == 200:
|
||||
if errcode is None:
|
||||
return res
|
||||
else:
|
||||
raise Exception( f"登陆错误码:{res}" )
|
||||
raise Exception( "请求login登陆时出错了" )
|
||||
|
||||
@classmethod
|
||||
def 获取手机号( cls, url, code: str ):
|
||||
"""
|
||||
@param url:
|
||||
@type url:
|
||||
@param code:
|
||||
@type code:
|
||||
@return: {'session_key': 'etC7EOml89Pd8XIhTFyAOA==', 'expires_in': 7200, 'openid': 'oNTAd0fm1GQAdohfbvywhGstsIPc', 'unionid': 'ojWkM1MnLrdty0S3Vks2pYorp4MY'}
|
||||
@rtype:
|
||||
"""
|
||||
data = {
|
||||
"code": code
|
||||
}
|
||||
res_data = post( url=url, json=data )
|
||||
res = res_data.json( ) # type: dict
|
||||
return res
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
# res = wei小程序服务端工具.获取OPENID(
|
||||
# js_code="0e1sYCkl2bCmhg4DG2ll2kheh10sYCkn", appid=paymentCategory.appid,
|
||||
# secret=paymentCategory.appsecret,
|
||||
# )
|
||||
@@ -0,0 +1,48 @@
|
||||
import json
|
||||
|
||||
import requests
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from wechat_token.token工具包 import sixToken
|
||||
|
||||
|
||||
class mini用工关系管理工具包(BaseModel):
|
||||
# demo_必填: int = Field(..., description="描述") # 必传属发生
|
||||
# demo_可选: Optional[int] = None # 可选值
|
||||
# demo_默认值: int = Field(default=100001, description="描述") # 有默认值
|
||||
# demo_联合类型: Literal["xiao", "zizi"] = Field(..., description="描述")
|
||||
tmplIds: int = Field(default="SFFgGF1bN56_KNd_4HicCFBBfu2PCRwCyQ5upr-EVk9Vdyl4wyyQ7B1mJvjceR29", description="绑定的模板ID账号")
|
||||
|
||||
def sendMini给绑定员工推送服务消息(self, touser_推送用户的openid: str):
|
||||
mini_token = sixToken.get_mini_token()
|
||||
|
||||
template_content = {
|
||||
"data": { # ⚠️ 这个外层 "data" 是微信接口强制要求的嵌套结构
|
||||
"thing1": {"value": "aaaaaaa"}, # 替换为模板实际字段名
|
||||
"amount1": {"value": "12"}, # 金额建议纯数字(无¥符号)
|
||||
},
|
||||
}
|
||||
|
||||
# 2. 转为JSON字符串(ensure_ascii=False保留中文,但微信要求最终是转义后的字符串)
|
||||
data_string = json.dumps(template_content, ensure_ascii=False)
|
||||
|
||||
msgData = requests.post(url="https://api.weixin.qq.com/cgi-bin/message/wxopen/employeerelationmsg/send",
|
||||
params={"access_token": mini_token, },
|
||||
json={"template_id": "SFFgGF1bN56_KNd_4HicCFBBfu2PCRwCyQ5upr-EVk9Vdyl4wyyQ7B1mJvjceR29", # 推送消息模板
|
||||
"page": "pageTabBar/Home/Home", # 跳转路径
|
||||
"touser": touser_推送用户的openid, # 被推送用户openid
|
||||
"data": data_string,
|
||||
})
|
||||
print(msgData.json())
|
||||
|
||||
def 解绑用工关系(self, touser_推送用户的openid):
|
||||
""""""
|
||||
mini_token = sixToken.get_mini_token()
|
||||
infoData = requests.post(url="https://api.weixin.qq.com/wxa/business/unbinduserb2cauthinfo", params={"access_token": mini_token, }, json={"openid_list": [touser_推送用户的openid]}, )
|
||||
print(infoData.json())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
# mini用工关系管理工具包().sendMini给绑定员工推送服务消息(touser_推送用户的openid="ohQkD7NJ5EASxdh6ajad4QR_6i78")
|
||||
mini用工关系管理工具包().解绑用工关系(touser_推送用户的openid="ohQkD7NJ5EASxdh6ajad4QR_6i78")
|
||||
@@ -0,0 +1,28 @@
|
||||
import os
|
||||
|
||||
import django
|
||||
|
||||
from sixGuoDjango.settings import base_dir_name
|
||||
|
||||
os.environ.setdefault(key="DJANGO_SETTINGS_MODULE", value=f"{base_dir_name}.settings")
|
||||
django.setup()
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from django.conf import settings
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
非对称密钥公钥文件 = Path(settings.BASE_DIR).joinpath("wechat_项目工具包/小程序管理后API/非对称密钥公钥匙.txt").read_text(encoding="utf-8")
|
||||
非对称密钥私钥文件 = Path(settings.BASE_DIR).joinpath("wechat_项目工具包/小程序管理后API/非对称密钥私钥.txt").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
class api认证(BaseModel):
|
||||
# demo_必填: int = Field(..., description="描述") # 必传属发生
|
||||
# demo_可选: Optional[int] = None # 可选值
|
||||
# demo_默认值: int = Field(default=100001, description="描述") # 有默认值
|
||||
# demo_联合类型: Literal["xiao", "zizi"] = Field(..., description="描述")
|
||||
|
||||
开放平台证书编号: str = Field(default="5330e9294308d338a5e145fd03d0ea6a")
|
||||
对称密钥编号: str = Field(default="e7cbfb20e66128744285df68f7d0a712")
|
||||
对称密钥原文: str = Field(default="7eTLm4kf0YQBepT3W8Z66gAvh91veExMYjQevnb5weA=", description="对称密钥密钥明文")
|
||||
非对称密钥编号: str = Field(default="73792fe24cfe9f3f0b7cb89594883e2f")
|
||||
@@ -0,0 +1,24 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID9DCCAtygAwIBAgIUaB2siA/X11h8JSI6eZy5JFqS9EIwDQYJKoZIhvcNAQEL
|
||||
BQAwbTELMAkGA1UEBhMCQ04xEjAQBgNVBAgMCUd1YW5nRG9uZzERMA8GA1UEBwwI
|
||||
U2hlblpoZW4xEDAOBgNVBAoMB1RlbmNlbnQxEDAOBgNVBAsMB1RlbmNlbnQxEzAR
|
||||
BgNVBAMMCnRzbTAwMDAwMDYwHhcNMjUwMzIxMDk0MzM5WhcNMzUwMzE5MDk0MzM5
|
||||
WjCBmDEeMBwGCSqGSIb3DQEJARYPd2VpeGlubXBAcXEuY29tMRswGQYDVQQDDBJ3
|
||||
eGY2MTJlODI1Mzc3MzI4ZjAxFTATBgNVBAoMDFRlbmNlbnQgSW5jLjEOMAwGA1UE
|
||||
CwwFV3hnTXAxCzAJBgNVBAYTAkNOMRIwEAYDVQQIDAlHdWFuZ0RvbmcxETAPBgNV
|
||||
BAcMCFNoZW5aaGVuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsKzj
|
||||
VjuulJIlMNUtNsbtczsR0tXD34987zaPDnv0aM7woQv9b9aOgZ8NSo+gOcxp9xXo
|
||||
hek/r2F+IZi4Wo2ojDugtDF4V/8BJbv+wgPkKWeK0q5zGSmoiGV+pyA6Th9c9Xwk
|
||||
X0TaOZl2sAXYYi0jdvzXt2MTrtkTv4HjsUG22svFc3D4SXXQ1MdWB4mWlevpSgqb
|
||||
T7GXrLg6GJ0sk5HZuaUMjL2n81SguvRq76GRsFVylgMjbaGj1xf4kiuo5MyJStp5
|
||||
xyPSxN2R23gg6NnLOJQOa3jZgqWPTnVF6l7J1xqdEtkK1F386y3hjP9IcB95VwLp
|
||||
sJRW7ZZmuabvDmy4tQIDAQABo2AwXjAdBgNVHQ4EFgQU+RzLjExXyXwuTRyzT9Vm
|
||||
RFdVCz4wHwYDVR0jBBgwFoAURM1183H4z2eJGmP5z/zWI7tjRZAwDAYDVR0TAQH/
|
||||
BAIwADAOBgNVHQ8BAf8EBAMCBsAwDQYJKoZIhvcNAQELBQADggEBAE0w60N+N9ZF
|
||||
nasTtkN4Ee8GQtDUNAqF0icuGKSp+HvgZB1svIxG1DCtkSe8coaq+wFojKrepW+c
|
||||
+eZXmmR55YlQSrqQa9YuPfAWNgJ5BvzBsRdUz2IyClAqQPtyAq22a9SwR3z3OGUu
|
||||
sQnoqFgi6jShgqOhdOmy92N/DoWEmIIlZJrchVUs2JLHuq9gaKZWMqrSUsTNrAi1
|
||||
lWbpmf9nDa92AhGtphMXU0tgYChFCYka3e0RPq5UQnMFPKH58tbIkqQqCeYKyVIT
|
||||
hHNyPaB/7M6jhrF0rwn2R5Crj3oW/+FuYITHH+Tg4G+XvXgkhyx7ZS/ZA9PI3qz7
|
||||
Tf68XuBEJoI=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,9 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+7fWfsUk5JceJXIG8a3J
|
||||
Zu30KdKv3uFVP+vJtYDiTspiOd1zl2D7fzTeZ3BNLlW29OGNDFlUEUJGoVSVzh9Z
|
||||
gKm4ZCpjQYSy03VH25kJHCjVZLX9aBy0DuRUN2Jwq/ma1Z9lgrJuo5Kou2oQQJgq
|
||||
XON20030CqNkiQA/bMJJnN1MYJZiI7eHdW/HIC42wB31YW0ZNACEP1UfQ4o54zJz
|
||||
bi6xNus9OyD2DtjV3Q0ZRjuRRUU5krBWeV2kK2XQrKJ0T5TF9q0N+QqJETA6iY33
|
||||
ScRqt25SDcpjc4dVuwSveL6fPj5JzrwfRkTtYwDlwfLz7yChJD4aJQ+NWvmyOMVW
|
||||
xQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEA+7fWfsUk5JceJXIG8a3JZu30KdKv3uFVP+vJtYDiTspiOd1z
|
||||
l2D7fzTeZ3BNLlW29OGNDFlUEUJGoVSVzh9ZgKm4ZCpjQYSy03VH25kJHCjVZLX9
|
||||
aBy0DuRUN2Jwq/ma1Z9lgrJuo5Kou2oQQJgqXON20030CqNkiQA/bMJJnN1MYJZi
|
||||
I7eHdW/HIC42wB31YW0ZNACEP1UfQ4o54zJzbi6xNus9OyD2DtjV3Q0ZRjuRRUU5
|
||||
krBWeV2kK2XQrKJ0T5TF9q0N+QqJETA6iY33ScRqt25SDcpjc4dVuwSveL6fPj5J
|
||||
zrwfRkTtYwDlwfLz7yChJD4aJQ+NWvmyOMVWxQIDAQABAoIBAGIvDAh4p3JOi6C5
|
||||
gIHmcqema1Wg6565MAJ120Dj2+I1yOifO4YxewfcPGAGiuRm5K1SCS1m7oao3lnx
|
||||
X1l+GvA3w0e2zO5ybzpSBcxfPhFw0AOvhP+GKqmgYEUWLbK28rEesn6vUL6W5x/r
|
||||
OBmqAOQoiWHUhztbDkzt/s9SXVpEpYxoEikg2M8QBGHTY+IuHFWMKSSltWh845zK
|
||||
MtSARPszxlU10WXH8xZIfTGK5EvZ6U5GHoWlDVnEJjzC5NAvf2tcrFP3suMEjEic
|
||||
PPEmZXR/FNAr7MNF118hsZ8FckUlt4ngaTwT0dmSqcutCc4ye9v4YpxFOyQPoBB3
|
||||
SnfsbMUCgYEA/gIWSKt4PBsBpWvDIHBEjwYCMwZM/xOqtjnAe1gVR1SDb/fMOr/6
|
||||
FDW14841I75ez6/GZxHTDzbkxsJ7NVcRrXxKGyxL0+J50qEPnFairyGVd501Dktd
|
||||
TghTGP5iffesPtRqs36oFURGCPaKetD8Ar33LjXhIzttsPCoJIs0ZbsCgYEA/bEn
|
||||
VeUicXqwRdarixCgvihMem9Jm0fSyaQc7aeN1xzbI4OEuwq0tWGwJ6UmZOJcbdxx
|
||||
lc7Z8Y6/U6Gv6oxFgHcOtB4cTHpBSOsKMzXcJNjVVkXzEuq6cvRIEYHZEdzN82t/
|
||||
2uMUh9DRyIzNDFJ4/+OEaiRQDT3yS8Z8/KLfLX8CgYEAv1cjIirIr75IRyweGNl5
|
||||
S6PjFDyy05MlDZADhuabYNgxCq9HkRn8cCzlj21OalaFZT2r3wh2TQe03mSyG0Qz
|
||||
dNQjnsSHel0oF5psAADjG7nYtfoedV4gMx/iOPmnh+yCO/IXTo8iXqLHfwj41/uF
|
||||
Wytuf8jmh3vE7XzbrGuQVPECgYAD9cL2bUg3W0HrJSmyVwUbUse21Yvhl6JrsVse
|
||||
PpJRPEsTZ9cnkEg32JmGVOafhoUI1fsT6pGuWpsT9B8p2xyun60kFKA+dV4VhE25
|
||||
dOrFzHiwCxF7Rxy39KY3V76Kr9arKQ9q3EaSxCiyJ7h/q73lyKhle8GgI0pn8mY7
|
||||
D1PKgwKBgCxqlcRlHbQ3pv1NM5efI2bEdg1Dz5zuxL7DU/lBoE10dvlyJgJRbRNZ
|
||||
x49nykTmM3cWjZStQBvxuqzo8XMOVvZaq9DuR0sDbNUDDI61Og/cAhtyThYsiPpL
|
||||
HzgEN+3o4mVn5+0w//t1JJNpEpmSA8h4aZ0esa8E3wNe+ZMfZ4ti
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -0,0 +1,466 @@
|
||||
"""
|
||||
Author: xiao 792282@qq.com
|
||||
Date: 2025-08-10 11:36:05
|
||||
LastEditors: xiao 792282@qq.com
|
||||
LastEditTime: 2025-08-17 13:38:39
|
||||
FilePath: django_微信支付/支付工具/微信支付类.py
|
||||
Description: 这是默认设置,可以在设置》工具》File Description中进行配置
|
||||
"""
|
||||
import os
|
||||
|
||||
import django
|
||||
|
||||
from sixGuoDjango.settings import base_dir_name
|
||||
|
||||
os.environ.setdefault(key="DJANGO_SETTINGS_MODULE", value=f"{base_dir_name}.settings")
|
||||
django.setup()
|
||||
|
||||
from pathlib import Path
|
||||
import requests
|
||||
from wechat_微信支付证书.江苏六果科技有限公司支付证书.江苏六果科技有限公司支付参数 import sixpay六果科技公司
|
||||
|
||||
import orjson
|
||||
from Crypto.Util.Padding import unpad
|
||||
from django.conf import settings
|
||||
|
||||
"""微信支付清爽版本"""
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import secrets
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Optional, Literal
|
||||
|
||||
import pydash
|
||||
import xmltodict
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Hash import SHA256
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.PublicKey.RSA import RsaKey
|
||||
from Crypto.Signature import pkcs1_15
|
||||
from dicttoxml import dicttoxml
|
||||
from pydantic import BaseModel
|
||||
from requests import Response, post, get
|
||||
|
||||
|
||||
class paymentCategory(sixpay六果科技公司):
|
||||
"""1"""
|
||||
pass
|
||||
|
||||
|
||||
class wechatPay(BaseModel):
|
||||
payment: type[paymentCategory] | type[sixpay六果科技公司]
|
||||
|
||||
def 构造签名串(self, uri: str, body_data: dict | bytes | str | None = None,
|
||||
method: Optional[Literal["GET", "POST"]] = "POST",
|
||||
):
|
||||
"""
|
||||
构造签名串
|
||||
@param url:
|
||||
@type url:
|
||||
@param body:
|
||||
@type body:
|
||||
@param method:
|
||||
@type method:
|
||||
@return: 返回已经加密后并组装好的请求头;
|
||||
@rtype:
|
||||
"""
|
||||
url_data = re.search(r'/v3.*', string=uri).group()
|
||||
shi_jian_chou = str(int(time.time())) # 时间戳
|
||||
random_str = secrets.token_hex(nbytes=16) # 随机字符串
|
||||
|
||||
# 创建签名请求参数
|
||||
|
||||
signaturestring: str | None = None
|
||||
if isinstance(body_data, str):
|
||||
signaturestring = f"{method}\n{url_data}\n{shi_jian_chou}\n{random_str}\n{body_data}\n"
|
||||
if isinstance(body_data, dict):
|
||||
body_inner = json.dumps(obj=body_data)
|
||||
signaturestring = f"{method}\n{url_data}\n{shi_jian_chou}\n{random_str}\n{body_inner}\n"
|
||||
if body_data is None:
|
||||
body_inner = ""
|
||||
signaturestring = f"{method}\n{url_data}\n{shi_jian_chou}\n{random_str}\n{body_inner}\n"
|
||||
if isinstance(body_data, bytes):
|
||||
body_inner = body_data.decode()
|
||||
signaturestring = f"{method}\n{url_data}\n{shi_jian_chou}\n{random_str}\n{body_inner}\n"
|
||||
|
||||
# 构造一个哈希对象
|
||||
hash_obj = SHA256.new(signaturestring.encode(encoding="utf-8"))
|
||||
# 加载商户私钥
|
||||
# 调用了 RSA.import_key() 方法,它会从字符串或文件中导入一个 PEM 格式的 RSA 私钥,并返回一个 RsaKey 类型的对象
|
||||
private_key: RsaKey = RSA.import_key(self.payment.private_key)
|
||||
# 生成签名
|
||||
signature = pkcs1_15.new(rsa_key=private_key).sign(msg_hash=hash_obj)
|
||||
# 对签名进行Base64编码,符合微信支付API要求
|
||||
encoded_signature = base64.b64encode(s=signature).decode()
|
||||
# 组装成微信官方需要的数据;
|
||||
data = {
|
||||
"mchid": self.payment.mchid,
|
||||
"nonce_str": random_str,
|
||||
"serial_no": self.payment.serial_no,
|
||||
"timestamp": shi_jian_chou,
|
||||
"signature": encoded_signature,
|
||||
} # 组装好的字典数据;
|
||||
ftr = "WECHATPAY2-SHA256-RSA2048"
|
||||
s_with_space = ftr.ljust(len(ftr) + 1) # 将字符串长度 +1(填充空格)
|
||||
str_list = ",".join([f'{key}="{value}"' for key, value in data.items()])
|
||||
# 生成请求头
|
||||
Authorization = f"{s_with_space}{str_list}"
|
||||
headers = {
|
||||
"Content-Type": "application/json", "Authorization": Authorization,
|
||||
}
|
||||
return headers
|
||||
|
||||
def getpay_jsapi(self, uri, body_data: dict | bytes | str | None = None, method: Optional[
|
||||
Literal["GET", "POST"]] = "POST",
|
||||
):
|
||||
"""
|
||||
@param url: 请求支付的微信官方地址
|
||||
@type url: str
|
||||
@param pay_body: 小程序下单接口获取到发起支付的必要参数prepay_id
|
||||
@type pay_body: str
|
||||
@return:
|
||||
@rtype:
|
||||
"""
|
||||
|
||||
headers = self.构造签名串(uri=uri, body_data=body_data, method=method)
|
||||
# 发送请求
|
||||
response: Response = requests.post(url=uri, json=body_data, headers=headers)
|
||||
print(response.json())
|
||||
prepay_id = pydash.get(obj=response.json(), path="prepay_id", default=None)
|
||||
# 验证请求后返回的数据是否为微信官方所发送;
|
||||
验证签名结果 = self.验证签名串(response_data=response.json(), response_headers=response.headers)
|
||||
if 验证签名结果:
|
||||
prepay_id = f"prepay_id={prepay_id}"
|
||||
# 调用方法生成返回给前端的对象
|
||||
return self.返回前端支付数据(prepay_id=prepay_id,
|
||||
wechat_appid=self.payment.appid, ) # 调用 返回前端支付数据()方法生成返回前端的支付字典
|
||||
else:
|
||||
raise "验证不通过"
|
||||
|
||||
def modegmc解密(self, nonce: str, ciphertext: str, associated_data: str, ) -> dict:
|
||||
"""
|
||||
支付成功或者退款回调时需要解密参数,微信返回的响应体.解密后可以获取支付成功或者退款的详细的数据;
|
||||
@param nonce: // 加密使用的随机串初始化向量
|
||||
@type nonce:
|
||||
@param ciphertext: // Base64编码后的密文(需要解密)
|
||||
@type ciphertext: (需要解密)
|
||||
@param associated_data: // 附加数据包(可能为空)
|
||||
@type associated_data:
|
||||
@return:
|
||||
@rtype:
|
||||
"""
|
||||
key_bytes = self.payment.apiv3.encode()
|
||||
nonce_bytes = nonce.encode(encoding="utf-8")
|
||||
ad_bytes = associated_data.encode(encoding="utf-8")
|
||||
encrypted_data = base64.b64decode(ciphertext)
|
||||
|
||||
cipher = AES.new(key=key_bytes, mode=AES.MODE_GCM, nonce=nonce_bytes)
|
||||
# 处理附加关联数据(AAD) - 微信支付中通常是"transaction"
|
||||
# 这部分数据在加密过程中被验证但未被加密
|
||||
# 必须与加密时使用的附加数据完全一致
|
||||
cipher.update(assoc_data=ad_bytes)
|
||||
auth_tag = encrypted_data[-16:] # 提取认证标签(最后16 字节)
|
||||
# 提取实际加密内容
|
||||
ciphertext_bytes = encrypted_data[:-16] # 提取实际加密内容
|
||||
decrypted_data = cipher.decrypt_and_verify(ciphertext=ciphertext_bytes, received_mac_tag=auth_tag)
|
||||
# 回调解密数据: dict = json.loads(decrypted_data.decode())
|
||||
回调解密数据: dict = orjson.loads(decrypted_data.decode(encoding="utf-8"))
|
||||
return 回调解密数据
|
||||
|
||||
def getpay_navite(self, navite_body: dict, ):
|
||||
"""
|
||||
@param navite_body:
|
||||
@type navite_body:
|
||||
@return:
|
||||
@rtype:
|
||||
"""
|
||||
native_url = "https://api.mch.weixin.qq.com/v3/pay/transactions/native"
|
||||
headers = self.构造签名串(body_data=navite_body, uri=native_url, method="POST")
|
||||
response = post(url=native_url, headers=headers, json=navite_body)
|
||||
生成的支付链接 = response.json()
|
||||
print(生成的支付链接)
|
||||
return 生成的支付链接
|
||||
|
||||
def callBack回调解密(self, headers, response_body: dict) -> dict:
|
||||
"""
|
||||
执行微信支付回调方法(获取返回的参数数据)
|
||||
@param request:
|
||||
@type request:
|
||||
"""
|
||||
# response_data = response_body.decode()
|
||||
# 调用验证签名串方法,(必需要验证确保是微信官方的回调数据;)
|
||||
验证签名串结果数据 = self.验证签名串(response_headers=headers, response_data=response_body)
|
||||
print("🍎 🍏🍎 🍏🍎 🍏🍎 🍏🍎 🍏🍎 🍏🍎 🍏🍎 🍏🍎 🍏🍎 🍏🍎 🍏", 验证签名串结果数据)
|
||||
# 验证成功后;拿到返回的json数据
|
||||
# res_data = json.loads(s=response_data) # TODO 一定要序列化.
|
||||
ciphertext = pydash.get(obj=response_body, path="resource.ciphertext", default=None)
|
||||
associated_data = pydash.get(obj=response_body, path="resource.associated_data", default=None)
|
||||
nonce = pydash.get(obj=response_body, path="resource.nonce", default=None)
|
||||
wei_xin_zhi_fu_hui_diao_data = self.modegmc解密(
|
||||
nonce=nonce, ciphertext=ciphertext,
|
||||
associated_data=associated_data,
|
||||
)
|
||||
return wei_xin_zhi_fu_hui_diao_data
|
||||
|
||||
def 返回前端支付数据(self, prepay_id: str, wechat_appid: str):
|
||||
"""
|
||||
小程序调起支付
|
||||
通过JSAPI下单接口获取到发起支付的必要参数prepay_id,然后使用微信支付提供的小程序方法调起小程序支付。
|
||||
@param prepay_ids
|
||||
@type prepay_id:
|
||||
@return:
|
||||
@rtype:
|
||||
"""
|
||||
shi_jian_chou = str(int(time.time())) # 生在时间戳
|
||||
random_string = secrets.token_hex(nbytes=30) # 随机字符串
|
||||
# 组装微信官方要求的格式;
|
||||
qian_mian_chuan = f"{wechat_appid}\n{shi_jian_chou}\n{random_string}\n{prepay_id}\n"
|
||||
# # 使用组装好地签名串,进行sha256 哈希生成哈希对象
|
||||
hash_obj = SHA256.new(qian_mian_chuan.encode())
|
||||
# 生成商户私钥对象
|
||||
# private_key = RSA.import_key( wechat_config.wechat私钥 )
|
||||
private_key = RSA.import_key(self.payment.private_key)
|
||||
# 生成签名
|
||||
signature = pkcs1_15.new(private_key).sign(hash_obj)
|
||||
# 将生成的签名串,转换为base格式
|
||||
encoded_signature = base64.b64encode(signature).decode()
|
||||
|
||||
data_obj = {
|
||||
"appId": self.payment.appid,
|
||||
"timeStamp": shi_jian_chou,
|
||||
"nonceStr": random_string,
|
||||
"package": prepay_id,
|
||||
"signType": "RSA",
|
||||
"paySign": encoded_signature,
|
||||
}
|
||||
return {
|
||||
"response": data_obj,
|
||||
}
|
||||
|
||||
def transferAccounts商家转账到零钱(self, transferAccountsBody):
|
||||
"""
|
||||
向微信用户发起转账接口;(实测已经领到钱了)前端直接调用 wx就可以,(目前没有解决ts报错的 问题)
|
||||
"""
|
||||
# uri = "https://api.mch.weixin.qq.com/v3/transfer/batches" # (旧接口)
|
||||
uri = "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills"
|
||||
# transferAccountsBody = {
|
||||
# "appid": self.payment.appid, "out_bill_no": order_random, # 必填 integer【商户单号】
|
||||
# # 商户系统内部的商家单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
|
||||
# "transfer_scene_id": "1000",
|
||||
# # 必填 integer【转账场景ID】 该笔转账使用的转账场景,可前往“商户平台-产品中心-商家转账”中申请。如:1000(现金营销),1006(企业报销)等
|
||||
# # "openid":"ohQkD7A-N93kljcFmkCyG6e_D9vg",
|
||||
# "openid": mini_openid, # 必填 integer【收款用户OpenID】 用户在商户appid下的唯一标识。发起转账前需获取到用户的OpenID,获取方式详见参数说明。
|
||||
# # "user_name":"757b340b45ebef5467rter35gf464344v3542sdf4t6re4tb4f54ty45t4yyry45", # 【收款用户姓名】
|
||||
# # 收款方真实姓名。需要加密传入,支持标准RSA算法和国密算法,公钥由微信侧提供。
|
||||
# "transfer_amount": transfer_amount, # 必填 integer【转账金额】 转账金额单位为“分”。
|
||||
# "transfer_remark": "新会员开通有礼", # 必填 string(32)【转账备注】 转账备注,用户收款时可见该备注信息,UTF8编码,最多允许32个字符
|
||||
# "notify_url": "https://www.sixgou.site", # 选填【通知地址】 异步接收微信支付结果通知的回调地址,通知url必须为公网可访问的URL,必须为HTTPS,不能携带参数。
|
||||
# "user_recv_perception": "现金奖励", # 选填 【用户收款感知】 用户收款时感知到的收款原因将根据转账场景自动展示默认内容。如有其他展示需求,
|
||||
# # 必填 array[TransferSceneReportInfo] 【转账场景报备信息】 各转账场景下需报备的内容,商户需要按照所属转账场景规则传参,详见转账场景报备信息字段说明。
|
||||
# "transfer_scene_report_infos": [
|
||||
# {
|
||||
# "info_type": "活动名称", # 必填 string(15)【信息类型】 不能超过15个字符,商户所属转账场景下的信息类型,此字段内容为固定值
|
||||
# "info_content": "新会员有礼", # 必填 【信息内容】 不能超过32个字符,商户所属转账场景下的信息内容,商户可按实际业务场景自定义传参
|
||||
# }, {
|
||||
# "info_type": "奖励说明", "info_content": "注册会员抽奖一等奖",
|
||||
# },
|
||||
# ],
|
||||
# }
|
||||
headers = self.构造签名串(uri=uri, body_data=transferAccountsBody, method="POST")
|
||||
data = post(url=uri, json=transferAccountsBody, headers=headers)
|
||||
package = data.json()
|
||||
package_info = pydash.get(obj=package, path="package_info")
|
||||
前端调用的数据 = {
|
||||
"mchId": self.payment.mchid, "appId": self.payment.appid, "package": package_info,
|
||||
}
|
||||
# return 前端调用的数据
|
||||
return package
|
||||
"""
|
||||
前端调用的示例
|
||||
const demo_fun = () => {
|
||||
wx.requestMerchantTransfer({
|
||||
// 你的参数
|
||||
mchId: '1700383419',
|
||||
appId: "wxf612e825377328f0",
|
||||
package: "ABBQO+oYAAABAAAAAAB99dFZt82j95u0HhbyZxAAAADnGpepZahT9IkJjn90
|
||||
+1qgGS6abB6UNQ9r1BZy26rL41bBjlwNMlDnYxPA/nGPWXtoOeFekxLE3iayC1CrxUngRWxwI/X79IcQ1+mBcUFwAUn3+f8=",
|
||||
success: (res: any) => {
|
||||
// res.err_msg将在页面展示成功后返回应用时返回ok,并不代表付款成功
|
||||
console.log('success:', res);
|
||||
},
|
||||
});
|
||||
}
|
||||
"""
|
||||
|
||||
def transferAccounts商家转账查询(self, transfer_bill_no: str):
|
||||
"""
|
||||
:param transfer_bill_no:
|
||||
:type transfer_bill_no:
|
||||
"""
|
||||
uri = (f"https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/transfer-bill-no/"
|
||||
f"{transfer_bill_no}")
|
||||
header = self.构造签名串(method="GET", uri=uri)
|
||||
data = get(url=uri, headers=header)
|
||||
print(data.json())
|
||||
|
||||
def 验证签名串(self, response_headers, response_data: dict, ):
|
||||
"""
|
||||
@param response_headers:
|
||||
@type response_headers:
|
||||
@param response_data:
|
||||
@type response_data:
|
||||
@return:
|
||||
@rtype:
|
||||
"""
|
||||
public_key = RSA.import_key(self.payment.wechat_certificate)
|
||||
Wechatpay_Timestamp = pydash.get(response_headers, "Wechatpay-Timestamp")
|
||||
Wechatpay_Nonce = pydash.get(response_headers, "Wechatpay-Nonce")
|
||||
Wechatpay_Signa = pydash.get(response_headers, "Wechatpay-Signature")
|
||||
response_data_str = orjson.dumps(response_data, ).decode(encoding="utf-8")
|
||||
string_to_verify = f"{Wechatpay_Timestamp}\n{Wechatpay_Nonce}\n{response_data_str}\n" # 构造验签名串
|
||||
hash_obj = SHA256.new(string_to_verify.encode())
|
||||
# 6. 验证签名
|
||||
try:
|
||||
pkcs1_15.new(public_key).verify(msg_hash=hash_obj, signature=base64.b64decode(Wechatpay_Signa))
|
||||
return True # 签名验证成功
|
||||
except (ValueError, TypeError) as e:
|
||||
raise e # 签名验证失败
|
||||
|
||||
def v2签名返回XML(self, sign_data: dict):
|
||||
"""v2版本的签名:将扫码支付的body转换为微信支付所要求的数据类型"""
|
||||
# sorted_dict = dict(sorted(sign_data.items(), key=lambda item: item[0], reverse=False))
|
||||
sorted_dict = dict(pydash.sort_by(collection=sign_data.items(), iteratee=0))
|
||||
# 拼接字符串: (将密钥添加到最末尾)
|
||||
params_str = "&".join(f"{k}={v}" for k, v in sorted_dict.items()) + '&key=' + self.payment.apiv2
|
||||
# 得到params_str就是需要 参与 签名的字符串
|
||||
signature = hashlib.md5(string=params_str.encode()).hexdigest().upper() # 使用MD5加密模式
|
||||
sign_data.update(sign=signature) # 将sign(签名的数据添加到dict数据中)
|
||||
xml = dicttoxml(obj=sign_data, custom_root="xml", xml_declaration=False, attr_type=False, cdata=True)
|
||||
return xml
|
||||
|
||||
def getpay_code扫用户付款码(self, body_data, ) -> dict:
|
||||
"""
|
||||
商家扫用户的付款码,收款
|
||||
:param body_data:
|
||||
:type body_data:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
xml = self.v2签名返回XML(sign_data=body_data)
|
||||
fu_kuan_url = "https://api.mch.weixin.qq.com/pay/micropay"
|
||||
res = post(url=fu_kuan_url, data=xml)
|
||||
# 返回的数据 是 ISO-8859-1 类型
|
||||
responseStr = res.text.encode("ISO-8859-1")
|
||||
# 将请求得到的数据 转换为字典格式;
|
||||
xml_dict = xmltodict.parse(responseStr.decode())
|
||||
# 得到请求结果并进行解密
|
||||
print(xml_dict)
|
||||
return xml_dict
|
||||
|
||||
def 获取平台证书(self, ):
|
||||
"""
|
||||
@param save_path:
|
||||
@type save_path:
|
||||
@return:
|
||||
@rtype:
|
||||
"""
|
||||
# save_path为首次保存存商户平台证书的路径(首次使用小程序需要获取这个证书,)就是调用本类方法就可以获取
|
||||
|
||||
certificate_url = "https://api.mch.weixin.qq.com/v3/certificates"
|
||||
headers = self.构造签名串(uri=certificate_url, method="GET")
|
||||
print(headers)
|
||||
res = get(url=certificate_url, headers=headers, )
|
||||
data = json.loads(res.text)
|
||||
print(data)
|
||||
ciphertext: str = pydash.get(obj=data, path="data[0].encrypt_certificate.ciphertext")
|
||||
nonce: str = pydash.get(obj=data, path="data[0].encrypt_certificate.nonce")
|
||||
associated_data: str = pydash.get(obj=data, path="data[0].encrypt_certificate.associated_data")
|
||||
|
||||
ciphertext_data = base64.b64decode(ciphertext.encode())
|
||||
# 调用后得到证书信息,需要保存起来
|
||||
cipher = AES.new(key=self.payment.apiv3.encode(), mode=AES.MODE_GCM, nonce=nonce.encode())
|
||||
# 处理附加关联数据(AAD) - 微信支付中通常是"transaction"
|
||||
# 这部分数据在加密过程中被验证但未被加密
|
||||
# 必须与加密时使用的附加数据完全一致
|
||||
cipher.update(associated_data.encode())
|
||||
auth_tag = ciphertext_data[-16:] # 提取认证标签(最后16 字节)
|
||||
# 提取实际加密内容
|
||||
ciphertext_bytes = ciphertext_data[:-16] # 提取实际加密内容
|
||||
decrypted_data = cipher.decrypt_and_verify(ciphertext=ciphertext_bytes, received_mac_tag=auth_tag)
|
||||
print(decrypted_data.decode(), "decrypted_data=====================")
|
||||
save_path = Path(settings.BASE_DIR).joinpath(
|
||||
"wechat_微信支付证书/江苏六果科技有限公司支付证书/sixgou_ping_tai_zheng_shu_cert.pem",
|
||||
).write_text(decrypted_data.decode())
|
||||
|
||||
def 解密群号(self, encryptedData, iv, sessionKey):
|
||||
"""
|
||||
对称解密使用的算法为 AES-128-CBC,数据采用PKCS#7填充。
|
||||
对称解密的目标密文为 Base64_Decode(encryptedData)。
|
||||
对称解密秘钥 aeskey = Base64_Decode(session_key), aeskey 是16字节。
|
||||
对称解密算法初始向量 为Base64_Decode(iv),其中iv由数据接口返回。
|
||||
:param sessionKey:
|
||||
:param encryptedData:
|
||||
:param iv:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
session_key_bytes = base64.b64decode(sessionKey)
|
||||
encrypted_data_bytes = base64.b64decode(encryptedData)
|
||||
iv_bytes = base64.b64decode(iv)
|
||||
cipher = AES.new(key=session_key_bytes, mode=AES.MODE_CBC, iv=iv_bytes)
|
||||
# 解密并去除填充
|
||||
decrypted_data = unpad(cipher.decrypt(encrypted_data_bytes), AES.block_size).decode()
|
||||
data_dict = orjson.loads(decrypted_data)
|
||||
openGId = pydash.get(obj=data_dict, path="openGId")
|
||||
timestamp = pydash.get(obj=data_dict, path="watermark.timestamp")
|
||||
appid = pydash.get(obj=data_dict, path="watermark.appid")
|
||||
return openGId, timestamp, appid
|
||||
except Exception as e:
|
||||
raise Exception(f"解密失败: {str(e)}")
|
||||
|
||||
def fai发放代金券(self, openid):
|
||||
"""
|
||||
|
||||
:param openid:
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
date = datetime.now().strftime("%Y%m%d")
|
||||
random_str = secrets.token_hex(6)
|
||||
example_out_request_no = f"{paymentCategory.mchid}{date}{random_str}"
|
||||
post_data = {
|
||||
"stock_id": "21056560", # 【创建批次的商户号】批次创建方商户号。校验规则:接口传入的批次号需由stock_creator_mchid所创建
|
||||
"out_request_no": example_out_request_no, # 【商户单据号】商户此次发放凭据号(格式:商户id+日期+流水号),商户侧需保持唯一性
|
||||
"appid": paymentCategory.appid,
|
||||
"stock_creator_mchid": paymentCategory.mchid, # 【创建批次的商户号】批次创建方商户号。
|
||||
# "coupon_value": 300, # 【指定面额发券,面额】指定面额发券场景,券面额,其他场景不需要填,单位:分。
|
||||
# "coupon_minimum": 300, # 【指定面额发券,券门槛】指定面额发券批次门槛,其他场景不需要,单位:分
|
||||
}
|
||||
uri = f"https://api.mch.weixin.qq.com/v3/marketing/favor/users/{openid}/coupons"
|
||||
headers = self.构造签名串(uri=uri, body_data=post_data, method="POST")
|
||||
result_data = post(url=uri, json=post_data, headers=headers)
|
||||
print(result_data.json())
|
||||
return result_data.json()
|
||||
|
||||
def 发起退款(self, body):
|
||||
pass
|
||||
uri = f"https://api.mch.weixin.qq.com//v3/refund/domestic/refunds"
|
||||
heardes = self.构造签名串(body_data=body, uri=uri, method="POST")
|
||||
print(heardes)
|
||||
response_data = requests.post(url=uri, json=body, headers=heardes)
|
||||
print(response_data.json())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
|
||||
# wechat_data = wechatPay(payment=paymentCategory).获取平台证书()
|
||||
wechat_data = wechatPay(payment=paymentCategory).fai发放代金券(openid="o6oV61xoMYAFNpi4RpVjn3BFGB1U")
|
||||
# 方法已经封装了请求结果,直接获得就是请求的结果 url (支付的url) 再由前端生成二维码
|
||||
# wechat_data = wechatPay(payment=sixpay六果科技公司).navite用户扫生成的二维码(navite_body=native_body)
|
||||
# headers = wechatPay(payment=sixpay六果科技公司).构造签名串(uri=native_url, method="POST", body_data=native_body)
|
||||
|
||||
# print(headers)
|
||||
@@ -0,0 +1,64 @@
|
||||
import hashlib
|
||||
import hmac
|
||||
from datetime import datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pydash
|
||||
from pydantic import BaseModel, Field
|
||||
from ulid import ULID
|
||||
|
||||
from wechat_微信支付证书.江苏六果科技有限公司支付证书.江苏六果科技有限公司支付参数 import sixpay六果科技公司
|
||||
|
||||
|
||||
class stock发送代金券功能(BaseModel):
|
||||
# demo_必填: int = Field(..., description="描述") # 必传属发生
|
||||
# demo_可选: Optional[int] = None # 可选值
|
||||
# demo_默认值: int = Field(default=100001, description="描述") # 有默认值
|
||||
# demo_联合类型: Literal["xiao", "zizi"] = Field(..., description="描述")
|
||||
pass
|
||||
stock_id_代金券批次号: str = Field(..., description="发放代金券是创建的批次号")
|
||||
|
||||
def send发券并返回前端需要的数据(self):
|
||||
"""前端请求:将这个字典对象返回给前端,前端调用小程序原生插件领取券"""
|
||||
pass
|
||||
# 第一步:准备数据
|
||||
out_request_no = str(ULID()) # 商家侧的发券订单号
|
||||
rfc3339 = datetime.now().astimezone(tz=ZoneInfo("Asia/Shanghai")).isoformat() # 代金券发券时的时间,因为代金券设置了起效的时间
|
||||
mchid = sixpay六果科技公司.mchid
|
||||
apiv2_key = sixpay六果科技公司.apiv2
|
||||
# 第二步: 创建好需要的数据并验名
|
||||
params每一张券需要验名的数据格式 = {
|
||||
"out_request_no0": out_request_no, # 商家侧的发券订单号
|
||||
"stock_id0": "22512292", # 代金券创建的批次号
|
||||
"send_coupon_merchant": mchid, # 发券的商家商户号
|
||||
# 如果是支付券,必须包含此项并参与签名;商家券则去掉
|
||||
"create_coupon_merchant0": mchid, # 发券的商家商户号
|
||||
"customize_send_time0": rfc3339, # 当前的领券的时间
|
||||
}
|
||||
|
||||
# iteratee为0是按key iteratee是以value的值来排序 reverse=False(从小到大) reverse=True(从大到小)
|
||||
sorted_dict = dict(pydash.sort_by(collection=params每一张券需要验名的数据格式.items(), iteratee=0, reverse=False))
|
||||
print(sorted_dict)
|
||||
# 拼接组装好的数据
|
||||
sign_str = "&".join([f"{k}={v}" for k, v in sorted_dict.items() if v]) + f"&key={apiv2_key}"
|
||||
# # 4. 生成 HMAC-SHA256 签名并转大写
|
||||
sign = hmac.new(key=apiv2_key.encode("utf-8"), msg=sign_str.encode("utf-8"), digestmod=hashlib.sha256, ).hexdigest().upper()
|
||||
# 3. 拼接参数串
|
||||
params = {
|
||||
"out_request_no": out_request_no,
|
||||
"stock_id": "22512292",
|
||||
"create_coupon_merchant": sixpay六果科技公司.mchid,
|
||||
"customize_send_time": rfc3339,
|
||||
"send_coupon_merchant": sixpay六果科技公司.mchid,
|
||||
}
|
||||
send_coupon_params: list[dict] = [params, ] # 发放代金券需要返回给前端的参数必须为列表 list[dict]
|
||||
return {
|
||||
"send_coupon_params": send_coupon_params,
|
||||
"sign": sign,
|
||||
"send_coupon_merchant": sixpay六果科技公司.mchid,
|
||||
"suggest_immediate_use": True,
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(00000000)
|
||||
@@ -0,0 +1,249 @@
|
||||
import os
|
||||
|
||||
import django
|
||||
import pydash
|
||||
|
||||
from sixGuoDjango.settings import base_dir_name
|
||||
|
||||
os.environ.setdefault(key="DJANGO_SETTINGS_MODULE", value=f"{base_dir_name}.settings")
|
||||
django.setup()
|
||||
from wechat_数据库.支付订单数据库.商品订单数据 import order订单
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Literal
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from wechat_数据库.用户数据库.小程序登陆用户 import weiuser
|
||||
from wechat_数据库.用户数据库.小程序积分数据库 import share积分表
|
||||
|
||||
|
||||
class miniUser提现状态(BaseModel):
|
||||
# demo_必填: int = Field(..., description="描述") # 必传属发生
|
||||
# demo_可选: Optional[int] = None # 可选值
|
||||
# demo_默认值: int = Field(default=100001, description="描述") # 有默认值
|
||||
# demo_联合类型: Literal["xiao", "zizi"] = Field(..., description="描述")
|
||||
不可提现: str = Field("不可提现", description="不可提现")
|
||||
可提现: str = Field("可提现", description="可提现")
|
||||
提现中: str = Field("提现中", description="提现中")
|
||||
|
||||
|
||||
class miniUser用户角色(BaseModel):
|
||||
# demo_必填: int = Field(..., description="描述") # 必传属发生
|
||||
# demo_可选: Optional[int] = None # 可选值
|
||||
# demo_默认值: int = Field(default=100001, description="描述") # 有默认值
|
||||
# demo_联合类型: Literal["xiao", "zizi"] = Field(..., description="描述")
|
||||
推广员: str = Field("推广员", description="推广员")
|
||||
企微员工: str = Field("企微员工", description="企微员工")
|
||||
代理商: str = Field("代理商", description="代理商")
|
||||
普通会员: str = Field("普通会员", description="普通会员")
|
||||
用工关系已绑定: str = Field("用工关系已绑定", description="用工关系已绑定")
|
||||
新人红包首次添加企微积分值: int = Field(100, description="新人红包首次添加企微积分值")
|
||||
发券奖励积分值: int = Field(10, description="添加通知好友积分")
|
||||
每日积分积分值: int = Field(50, description="发券奖励积分值")
|
||||
小程序分享积分值: int = Field(10, description="小程序分享积分值")
|
||||
|
||||
|
||||
class miniUser积分(BaseModel):
|
||||
share分享者unionidID: str = Field(..., description="分享者的unionid")
|
||||
click点击者unionidID: str = Field(..., description="点击者的的openid")
|
||||
bind绑定的企微unionidID: str = Field("", description="click绑定的企微unionidID")
|
||||
|
||||
def update更新用户头像(self):
|
||||
pass
|
||||
|
||||
def add添加每日积分(self, share积分类型: Literal["每日签到"], share分享者积分值: int, ):
|
||||
pass
|
||||
# 注意,这里的每日签到: 分享者 同时 也是点击者
|
||||
share分享者对象 = weiuser.objects.get(unionid=self.share分享者unionidID)
|
||||
click点击都对象 = weiuser.objects.get(unionid=self.click点击者unionidID)
|
||||
today当天的日期 = datetime.now(tz=ZoneInfo("Asia/Shanghai")).date()
|
||||
share对象, share布尔值 = share积分表.objects.update_or_create(
|
||||
share分享者unionid=self.share分享者unionidID, share分享者openid=share分享者对象.mini_openid, share点击者openid=click点击都对象.mini_openid, share点击者unionid=self.click点击者unionidID,
|
||||
share分享者积分值=share分享者积分值, share分享类型=share积分类型, share日期=today当天的日期, share积分是否可以提现的状态=miniUser提现状态().可提现,
|
||||
defaults={"share分享者头像": share分享者对象.user_头像, "share分享者昵称": share分享者对象.user_昵称,
|
||||
"share点击者头像": click点击都对象.user_头像, "share点击者昵称": click点击都对象.user_昵称
|
||||
},
|
||||
)
|
||||
|
||||
def add添加通知好友积分(self, share积分类型: Literal["通知好友", "发券奖励"], share分享者积分值: int = miniUser用户角色().发券奖励积分值, ):
|
||||
pass
|
||||
"""这个积分对应的是 小程序主页面 分享3元券的图片 """
|
||||
share分享者对象 = weiuser.objects.get(unionid=self.share分享者unionidID)
|
||||
click点击都对象 = weiuser.objects.get(unionid=self.click点击者unionidID)
|
||||
share对象, share布尔值 = share积分表.objects.update_or_create(
|
||||
share分享者unionid=self.share分享者unionidID, share分享者openid=share分享者对象.mini_openid,
|
||||
share点击者openid=click点击都对象.mini_openid, share点击者unionid=self.click点击者unionidID,
|
||||
share分享者积分值=share分享者积分值, share分享类型=share积分类型, share积分是否可以提现的状态=miniUser提现状态().可提现,
|
||||
defaults={"share分享者头像": share分享者对象.user_头像, "share分享者昵称": share分享者对象.user_昵称,
|
||||
"share点击者头像": click点击都对象.user_头像, "share点击者昵称": click点击都对象.user_昵称
|
||||
},
|
||||
)
|
||||
if share布尔值:
|
||||
print("发券奖励积分 添加成功")
|
||||
else:
|
||||
print("发券奖励积分 已经存在领过的")
|
||||
|
||||
def add添加用户分享商品推荐的积分(self, share积分类型: Literal["好物推荐"], share分享者积分值: int, share分享的商品ID: str):
|
||||
pass
|
||||
print("add添加用户商品详情页面分享积分调用了")
|
||||
share分享者对象 = weiuser.objects.get(unionid=self.share分享者unionidID)
|
||||
click点击都对象 = weiuser.objects.get(unionid=self.click点击者unionidID)
|
||||
six六果对象, sixBool = weiuser.objects.get_or_create(unionid="六果")
|
||||
if share分享者对象.parent == six六果对象 or share分享者对象.unionid == click点击都对象.unionid:
|
||||
pass
|
||||
else:
|
||||
share对象, share布尔值 = share积分表.objects.update_or_create(
|
||||
share分享者unionid=self.share分享者unionidID, share分享者openid=share分享者对象.mini_openid,
|
||||
share点击者openid=click点击都对象.mini_openid, share点击者unionid=self.click点击者unionidID,
|
||||
share分享者积分值=share分享者积分值, share分享类型=share积分类型, share分享的商品ID=share分享的商品ID,
|
||||
share积分是否可以提现的状态=miniUser提现状态().可提现,
|
||||
defaults={"share分享者头像": share分享者对象.user_头像, "share分享者昵称": share分享者对象.user_昵称,
|
||||
"share点击者头像": click点击都对象.user_头像, "share点击者昵称": click点击都对象.user_昵称
|
||||
},
|
||||
)
|
||||
# 。更新需要的数据
|
||||
|
||||
def add添加小程序推荐积分(self, share积分类型: Literal["小程序推荐"], share分享者积分值: int = miniUser用户角色().小程序分享积分值, ):
|
||||
pass
|
||||
share分享者对象 = weiuser.objects.get(unionid=self.share分享者unionidID)
|
||||
click点击都对象 = weiuser.objects.get(unionid=self.click点击者unionidID)
|
||||
share对象, share布尔值 = share积分表.objects.update_or_create(
|
||||
share分享者unionid=share分享者对象.unionid, share分享者openid=share分享者对象.mini_openid,
|
||||
share点击者openid=click点击都对象.mini_openid, share点击者unionid=self.click点击者unionidID,
|
||||
share分享者积分值=share分享者积分值, share分享类型=share积分类型, share积分是否可以提现的状态=miniUser提现状态().可提现,
|
||||
defaults={"share分享者头像": share分享者对象.user_头像, "share分享者昵称": share分享者对象.user_昵称,
|
||||
"share点击者头像": click点击都对象.user_头像, "share点击者昵称": click点击都对象.user_昵称
|
||||
},
|
||||
)
|
||||
|
||||
def add添加好友助力下单返利积分(self, share积分类型: Literal["好友下单"], order支付商家侧订单号):
|
||||
pass
|
||||
# 方法被调用的时候 第一步,需要更新当前点击的状态,
|
||||
# 将字符串转为换字典
|
||||
order对象 = order订单.objects.get(order支付商家侧订单号=order支付商家侧订单号) # 拿到当前订单的对象数据
|
||||
|
||||
share分享者unionidID = order对象.order分享者unionid
|
||||
click点击者unionidID = order对象.order支付者unionid
|
||||
share分享者对象 = weiuser.objects.get(unionid=share分享者unionidID)
|
||||
click点击都对象 = weiuser.objects.get(unionid=click点击者unionidID)
|
||||
six六果对象, sixBool = weiuser.objects.get_or_create(unionid="六果")
|
||||
# 通过订单号,拿到 第一个值:
|
||||
order购物车商品好友下单助力的推广员的积分值 = pydash.get(obj=pydash.head(order对象.order购物车商品数据), path="推荐积分.推广员") # 当前角色,对应的推广积分值
|
||||
order购物车商品好友下单助力的企微员工的的积分值 = pydash.get(obj=pydash.head(order对象.order购物车商品数据), path="推荐积分.企微员工") # 当前角色,对应的推广积分值
|
||||
order好友下单商品数量 = pydash.get(obj=pydash.head(order对象.order购物车商品数据), path="单个商品订单数量")
|
||||
order好友下单助力计算后的积分值 = int(order好友下单商品数量) * int(order购物车商品好友下单助力的推广员的积分值)
|
||||
order好友下单企微员工需要添加的积分值 = int(order好友下单商品数量) * int(order购物车商品好友下单助力的企微员工的的积分值)
|
||||
if share分享者unionidID == click点击者unionidID or share分享者对象.parent == six六果对象:
|
||||
print("自己点了自己,不需要添加积分了")
|
||||
if share分享者对象.user_角色权限 == miniUser用户角色().推广员:
|
||||
pass # 如果是推广员则添加
|
||||
share对象, share布尔值 = share积分表.objects.update_or_create(
|
||||
share分享者unionid=share分享者unionidID, share分享者openid=share分享者对象.mini_openid,
|
||||
share点击者unionid=click点击者unionidID, share点击者openid=click点击都对象.mini_openid,
|
||||
share分享者积分值=order好友下单助力计算后的积分值, share支付订单商家侧的订单号=order支付商家侧订单号,
|
||||
share分享类型=share积分类型, share积分是否可以提现的状态=miniUser提现状态().可提现,
|
||||
|
||||
defaults={"share分享者头像": share分享者对象.user_头像, "share分享者昵称": share分享者对象.user_昵称,
|
||||
"share点击者头像": click点击都对象.user_头像, "share点击者昵称": click点击都对象.user_昵称
|
||||
},
|
||||
)
|
||||
print("如果是推广员则添加如果是推广员则添加如果是推广员则添加如果是推广员则添加", share布尔值, share对象)
|
||||
else:
|
||||
share对象, share布尔值 = share积分表.objects.update_or_create(
|
||||
share分享者unionid=share分享者unionidID, share分享者openid=share分享者对象.mini_openid,
|
||||
share点击者unionid=click点击者unionidID, share点击者openid=click点击都对象.mini_openid,
|
||||
share分享者积分值=order好友下单助力计算后的积分值, share支付订单商家侧的订单号=order支付商家侧订单号,
|
||||
share分享类型=share积分类型, share积分是否可以提现的状态=miniUser提现状态().不可提现,
|
||||
defaults={"share分享者头像": share分享者对象.user_头像, "share分享者昵称": share分享者对象.user_昵称,
|
||||
"share点击者头像": click点击都对象.user_头像, "share点击者昵称": click点击都对象.user_昵称
|
||||
},
|
||||
)
|
||||
print("如果是推广员则添加如果是推广员则添加如果是推广员则添加如果是推广员则添加", share布尔值, share对象)
|
||||
if share分享者对象.user_企微角色权限 == miniUser用户角色().企微员工:
|
||||
print("给企微员工添加了积分了")
|
||||
share对象, share布尔值 = share积分表.objects.update_or_create(
|
||||
share分享者unionid=share分享者unionidID, share分享者openid=share分享者对象.mini_openid,
|
||||
share点击者unionid=click点击者unionidID, share点击者openid=click点击都对象.mini_openid,
|
||||
share分享者积分值=order好友下单企微员工需要添加的积分值, share支付订单商家侧的订单号=order支付商家侧订单号,
|
||||
share分享类型=share积分类型, share积分是否可以提现的状态=miniUser提现状态().可提现,
|
||||
defaults={"share分享者头像": share分享者对象.user_头像, "share分享者昵称": share分享者对象.user_昵称,
|
||||
"share点击者头像": click点击都对象.user_头像, "share点击者昵称": click点击都对象.user_昵称
|
||||
},
|
||||
)
|
||||
|
||||
def add添加点击者添加企微员工双向红包积分(self, share积分类型: Literal["新人红包"], share分享者二维码state: str):
|
||||
pass
|
||||
# 必须要先处理一下当前用户的状态,有一种可能性: 就是当前用户直接进入了小程序,所以当前用户的上级用户就是six对象;
|
||||
six六果对象, sixBool = weiuser.objects.get_or_create(unionid="六果")
|
||||
# 添加逻辑为: 能调用则说明用户是首次,还可以再添加一个判断条件
|
||||
share分享者对象 = weiuser.objects.get(unionid=self.share分享者unionidID) # 这里传递的是分享者,绑定的企微二维码state
|
||||
click点击都对象 = weiuser.objects.get(unionid=self.click点击者unionidID)
|
||||
bindRwecom企微员工对象 = weiuser.objects.get(unionid=self.bind绑定的企微unionidID)
|
||||
click点击者父元素 = click点击都对象.parent
|
||||
print("当前点击者的父元素是谁", click点击者父元素.unionid)
|
||||
print("添加企微客户的积分功能调用了啦")
|
||||
if click点击都对象.parent == six六果对象:
|
||||
print("===========这里判断生成了就需要更新当前用户的新状态了====处理的当前用户,进来的时候不是好友分享的状态,所以得更新,将当前用户的状态更到当前的企微用户名下============")
|
||||
click点击都对象.parent = bindRwecom企微员工对象
|
||||
click点击都对象.user_绑定企微UNIONID = bindRwecom企微员工对象.user_绑定企微UNIONID
|
||||
click点击都对象.user_用户当前的等级 = bindRwecom企微员工对象.user_用户当前的等级 + 1
|
||||
click点击都对象.codeRemark = bindRwecom企微员工对象.codeRemark
|
||||
click点击都对象.codeQrCode = bindRwecom企微员工对象.codeQrCode
|
||||
click点击都对象.codeConfigId = bindRwecom企微员工对象.codeConfigId
|
||||
click点击都对象.user_绑定企微usrID账号 = bindRwecom企微员工对象.user_绑定企微usrID账号
|
||||
click点击都对象.save()
|
||||
# 第一个逻辑,先判断,当前这个添加企微的 用户,是否已经领过红包了,如果领了过了就直接返回了
|
||||
is是否领过红包 = share积分表.objects.filter(share分享者unionid=click点击都对象.unionid, share分享类型="新人红包", share分享者积分值=100).exists()
|
||||
print("打印一下这里的值是不是已经存在了", is是否领过红包)
|
||||
if is是否领过红包:
|
||||
print("当前添加者已经领过新人红包了,直接return")
|
||||
return
|
||||
# 添加条件,分享者必须是推广员:
|
||||
# 添加 积分的条件一: 必须要满足,跟绑定的企微是同城的用户,如果不是同城用户,则不给任何的奖励
|
||||
if bindRwecom企微员工对象.user_区县 == click点击都对象.user_区县:
|
||||
# 这里是:给当前添加企微的用户 添加积分
|
||||
share对象, share布尔值 = share积分表.objects.update_or_create(
|
||||
share分享者unionid=click点击都对象.unionid, share分享者openid=click点击都对象.mini_openid,
|
||||
share点击者openid=click点击都对象.mini_openid, share点击者unionid=click点击都对象.unionid,
|
||||
share分享者积分值=100, share分享类型="新人红包", share积分是否可以提现的状态=miniUser提现状态().可提现,
|
||||
defaults={"share分享者头像": share分享者对象.user_头像, "share分享者昵称": share分享者对象.user_昵称,
|
||||
"share点击者头像": click点击都对象.user_头像, "share点击者昵称": click点击都对象.user_昵称
|
||||
},
|
||||
)
|
||||
print("添加企微者,并且是同城的情况下,可提现状态,", share布尔值)
|
||||
# 接着还需要处理,推广员, 并且还得是同城的情况下, 才可以给推广员添加积分了
|
||||
if click点击者父元素.user_角色权限 == miniUser用户角色().推广员 and bindRwecom企微员工对象.user_区县 == click点击者父元素.user_区县 == click点击都对象.user_区县:
|
||||
# 这里的状态为可以提现
|
||||
share对象, share布尔值 = share积分表.objects.update_or_create(
|
||||
share分享者unionid=click点击者父元素.unionid, share分享者openid=click点击者父元素.mini_openid,
|
||||
share点击者openid=click点击都对象.mini_openid, share点击者unionid=self.click点击者unionidID,
|
||||
share分享者积分值=300, share分享类型="荐客积分", share积分是否可以提现的状态=miniUser提现状态().可提现,
|
||||
defaults={"share分享者头像": share分享者对象.user_头像, "share分享者昵称": share分享者对象.user_昵称,
|
||||
"share点击者头像": click点击都对象.user_头像, "share点击者昵称": click点击都对象.user_昵称
|
||||
},
|
||||
)
|
||||
print("分享者,可提现状态", share布尔值)
|
||||
return
|
||||
# 在这里判断的是 给当前公司的企微推广员, 添加积分
|
||||
if bindRwecom企微员工对象.user_企微角色权限 == miniUser用户角色().企微员工 and bindRwecom企微员工对象.user_区县 == click点击都对象.user_区县:
|
||||
# 这里的状态为可以提现 这里的积分是给企微员工添加的,
|
||||
share对象, share布尔值 = share积分表.objects.update_or_create(
|
||||
share分享者unionid=bindRwecom企微员工对象.unionid, share分享者openid=bindRwecom企微员工对象.mini_openid,
|
||||
share点击者openid=click点击都对象.mini_openid, share点击者unionid=self.click点击者unionidID,
|
||||
share分享者积分值=300, share分享类型="荐客积分", share积分是否可以提现的状态=miniUser提现状态().可提现,
|
||||
defaults={"share分享者头像": share分享者对象.user_头像, "share分享者昵称": share分享者对象.user_昵称,
|
||||
"share点击者头像": click点击都对象.user_头像, "share点击者昵称": click点击都对象.user_昵称
|
||||
},
|
||||
)
|
||||
print("企微", share布尔值)
|
||||
return
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
# (miniUser积分(share分享者unionidID="", click点击者unionidID="")
|
||||
# .add添加好友助力下单返利积分(order支付商家侧订单号="OAT01KYDESHM9ADEXGY7BW0AVHFSG", share积分类型="好友下单", ))
|
||||
(miniUser积分(share分享者unionidID="oy4Jw6A1T3WkKOOhMxcbQTDSM8rs", click点击者unionidID="oy4Jw6P1FnuTNF-hSoJHNztSAoQ0", bind绑定的企微unionidID="oy4Jw6A1T3WkKOOhMxcbQTDSM8rs")
|
||||
.add添加点击者添加企微员工双向红包积分(share积分类型="新人红包", share分享者二维码state="oy4Jw6A1T3WkKOOhMxcbQTDSM8rs"))
|
||||
@@ -0,0 +1,195 @@
|
||||
"""功能模块"""
|
||||
import math
|
||||
from typing import TypedDict, Union
|
||||
|
||||
import jmespath
|
||||
import pydash
|
||||
import requests
|
||||
|
||||
|
||||
# WechatMapClass.haversine(lat1=32.
|
||||
# 474664, lon1=119.893471, lat2=32.
|
||||
# 471578, lon2=119.917068)
|
||||
# WechatMapClass.addressToCoordinat
|
||||
# eConversion(address="江苏省泰州市海陵区济川东路与青年南路交叉口东220米泰州万达广场(2号入口)")
|
||||
# WechatMapClass.coordinateToAddressConversion(location="32.474664,119.893471")
|
||||
# WechatMapClass.coordinateToAddressConversion(location="32.471578,119.917068")
|
||||
class OriginPointOptional(TypedDict):
|
||||
经度: Union[float, str]
|
||||
纬度: Union[float, str]
|
||||
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class AdInfo(BaseModel):
|
||||
city: str
|
||||
adcode: int
|
||||
nation: str
|
||||
district: str
|
||||
province: str
|
||||
nation_code: int
|
||||
|
||||
|
||||
class Location(BaseModel):
|
||||
lat: float
|
||||
lng: float
|
||||
|
||||
|
||||
class Result(BaseModel):
|
||||
ip: str
|
||||
ad_info: AdInfo
|
||||
location: Location
|
||||
|
||||
|
||||
class IP解析地址模型Response(BaseModel):
|
||||
result: Result
|
||||
status: int
|
||||
message: str
|
||||
|
||||
|
||||
class wei地图工具:
|
||||
"""注释"""
|
||||
|
||||
key = "7SNBZ-FLN63-LRA3S-YJBAK-XSHQ3-TGF5E" # 腾讯地图的key
|
||||
|
||||
# key = "LREBZ-PH5R7-2NIXW-H7JDB-CVHTK-4YFUJ" # 莫大帅注册地地图的腾讯地图的key
|
||||
|
||||
@classmethod
|
||||
def 公式计算二点之间的距离(cls, orgin原点: OriginPointOptional, user原点: OriginPointOptional) -> float:
|
||||
""""""
|
||||
# 地球半径,单位为公里
|
||||
R = 6371.0
|
||||
# orgin原点: OriginPointOptional = {"纬度": 30.80377, "经度": 104.151812}
|
||||
# user原点: OriginPointOptional = {"纬度": 30.80377, "经度": 104.151812}
|
||||
# 将角度转换为弧度
|
||||
orgin纬度 = math.radians(orgin原点.get("纬度"))
|
||||
orgin经度 = math.radians(orgin原点.get("经度"))
|
||||
user纬度 = math.radians(user原点.get("纬度"))
|
||||
user经度 = math.radians(user原点.get("纬度"))
|
||||
|
||||
# 经纬度差值
|
||||
经度差 = user经度 - orgin经度
|
||||
纬度差 = user纬度 - orgin纬度
|
||||
print(经度差, 纬度差)
|
||||
|
||||
# Haversine公式
|
||||
a = math.sin(纬度差 / 2) ** 2 + math.cos(orgin纬度) * math.cos(user纬度) * math.sin(经度差 / 2) ** 2
|
||||
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
|
||||
# 计算两点间的距离
|
||||
distance = R * c
|
||||
print(f"The distance between two points is {distance} km.")
|
||||
return distance
|
||||
|
||||
@classmethod
|
||||
def 腾讯地图计算二点之间的距离(cls, toData终点: OriginPointOptional, fromData起点: OriginPointOptional):
|
||||
""""""
|
||||
fromStrDat = dict(sorted(fromData起点.items(), reverse=False))
|
||||
fromStr = ",".join(fromStrDat.values())
|
||||
toStrDat = dict(sorted(toData终点.items(), reverse=False))
|
||||
toStr = ",".join(toStrDat.values())
|
||||
print("排序后的值为", fromStr, toStr)
|
||||
data = {"key": cls.key, "from": fromStr, "to": toStr}
|
||||
url = f"https://apis.map.qq.com/ws/distance/v1/matrix?mode=driving"
|
||||
res_data = requests.post(url=url, data=data)
|
||||
print(res_data.json())
|
||||
res_json = res_data.json() # type: dict
|
||||
d = {
|
||||
'status': 0,
|
||||
'message': 'Success',
|
||||
'request_id': '9435209f1ee84219ae7826e65f577331',
|
||||
'result': {
|
||||
'rows': [{
|
||||
'elements': [{
|
||||
'distance': 3550, # 起点到终点的距离,单位:米
|
||||
'duration': 651, # 表示从起点到终点的结合路况的时间,秒为单位注:步行/骑行方式(不计算耗时)以及起终点附近没有道路造成无法计算时,不返回本此节点
|
||||
}],
|
||||
}],
|
||||
},
|
||||
}
|
||||
distance = jmespath.search(data=res_data.json(), expression="result.rows[0].elements[0].distance")
|
||||
return distance
|
||||
|
||||
@classmethod
|
||||
def 地址转换为坐标(cls, address: str):
|
||||
""""""
|
||||
url = f"https://apis.map.qq.com/ws/geocoder/v1/?address={address}"
|
||||
res = requests.get(url=url, params={"key": cls.key}, )
|
||||
d = {
|
||||
'status': 0,
|
||||
'message': 'Success',
|
||||
'request_id': '9b049a3ff13c4ac3bda3bb5a9fe0c0a8',
|
||||
'result': {
|
||||
'title': '泰州海陵万达广场',
|
||||
'location': { # 解析到的坐标(GCJ02坐标系)
|
||||
'lng': 119.917068, # 经度
|
||||
'lat': 32.471578, # 纬度
|
||||
},
|
||||
'ad_info': { # 行政区划信息 object 是
|
||||
'adcode': '321202', # string 是 行政区划代码,规则详见:行政区划代码说明
|
||||
},
|
||||
'address_components': {
|
||||
'province': '江苏省',
|
||||
'city': '泰州市',
|
||||
'district': '海陵区',
|
||||
'street': '济川东路',
|
||||
'street_number': '',
|
||||
},
|
||||
'similarity': 0.99,
|
||||
'deviation': 1000,
|
||||
'reliability': 7, # 可信度参考:值范围 1 <低可信> - 10 <高可信>
|
||||
'level': 11, # 解析精度级别,分为11个级别,一般>=9即可采用(定位到点,精度较高) 也可根据实际业务需求自行调整,完整取值表见下文。
|
||||
},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def 经纬度转换为地址(cls, location):
|
||||
""""""
|
||||
pass
|
||||
url = f'https://apis.map.qq.com/ws/geocoder/v1/?location={location}'
|
||||
res = requests.get(url=url, params={"key": cls.key}, )
|
||||
return res.json()
|
||||
|
||||
@classmethod
|
||||
def ip地址转为地址(cls, x_forwarded_for: str | bool, ):
|
||||
""""""
|
||||
url = "https://apis.map.qq.com/ws/location/v1/ip"
|
||||
response_data = requests.get(url=url, params={"key": cls.key, "ip": x_forwarded_for}, )
|
||||
data = response_data.json() # type: dict
|
||||
print(data)
|
||||
resData = pydash.omit(data, "request_id")
|
||||
d = {
|
||||
'status': 0,
|
||||
'message': 'Success',
|
||||
'request_id': '8805bff3303f42dd8a1cbcca3701cf63',
|
||||
'result': {
|
||||
'ip': '240e:3a3:b201:3f10:185c:efdb:c5b0:b95c',
|
||||
'location': {
|
||||
'lat': 32.49098,
|
||||
'lng': 119.91956,
|
||||
},
|
||||
'ad_info': {
|
||||
'nation': '中国',
|
||||
'province': '江苏省',
|
||||
'city': '泰州市',
|
||||
'district': '海陵区',
|
||||
'adcode': 321202,
|
||||
'nation_code': 156,
|
||||
},
|
||||
},
|
||||
}
|
||||
# nation = jmespath.search(data=data, expression="result.ad_info.nation", ) # 国家
|
||||
# province = jmespath.search(data=data, expression="result.ad_info.province", ) # 省份
|
||||
# city = jmespath.search(data=data, expression="result.ad_info.city", )
|
||||
# district = jmespath.search(data=data, expression="result.ad_info.district", ) # 区
|
||||
# adcode = jmespath.search(data=data, expression="result.ad_info.adcode", ) # 区号
|
||||
# ip = jmespath.search(data=data, expression="result.ip", )
|
||||
# lat = jmespath.search(data=data, expression="result.location.lat", )
|
||||
# lng = jmespath.search(data=data, expression="result.location.lng", )
|
||||
|
||||
return resData
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# wei地图工具.腾讯地图计算二点之间的距离(toData终点={"纬度": "30.80377", "经度": "104.151812"}, fromData起点={"纬度": "30.80377", "经度": "104.151812"})
|
||||
print(wei地图工具.ip地址转为地址(x_forwarded_for="119.84.150.100", ))
|
||||
@@ -0,0 +1,67 @@
|
||||
import threading
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
class ParameterizedSingletonMeta(type):
|
||||
"""
|
||||
参数化单例元类
|
||||
行为:
|
||||
- 相同 (类, args, kwargs) → 同一实例
|
||||
- 不同参数 → 不同实例
|
||||
"""
|
||||
|
||||
_instances: dict = {}
|
||||
_lock = threading.RLock()
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
# 1. kwargs 排序,保证顺序无关
|
||||
kwargs_key = tuple(sorted(kwargs.items()))
|
||||
|
||||
# 2. 构造不可变的 key
|
||||
key = (cls, args, kwargs_key)
|
||||
|
||||
# 3. 防御:确保所有参数都可 hash
|
||||
try:
|
||||
hash(key)
|
||||
except TypeError as e:
|
||||
raise ValueError(
|
||||
f"{cls.__name__} 的单例参数必须全部可 hash"
|
||||
) from e
|
||||
|
||||
with cls._lock:
|
||||
if key not in cls._instances:
|
||||
cls._instances[key] = super().__call__(*args, **kwargs)
|
||||
else:
|
||||
print(f"[HIT] 命中缓存实例: {key}")
|
||||
|
||||
return cls._instances[key]
|
||||
|
||||
@classmethod
|
||||
def get_all_instances(cls):
|
||||
"""调试用:返回当前已创建的所有实例"""
|
||||
return list(cls._instances.values())
|
||||
|
||||
|
||||
@dataclass
|
||||
class Demo(metaclass=ParameterizedSingletonMeta):
|
||||
"""
|
||||
示例:参数化单例对象
|
||||
"""
|
||||
|
||||
name: str
|
||||
|
||||
def __post_init__(self):
|
||||
print(f"正在初始化 Demo(name={self.name})")
|
||||
|
||||
def __str__(self):
|
||||
return f"<Demo name={self.name} id={id(self)}>"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=== 测试 1:相同参数 ===")
|
||||
d1 = Demo(name="config_A")
|
||||
d2 = Demo(name="config_A")
|
||||
print(d1)
|
||||
print(d2)
|
||||
print("是否同一实例:", d1 is d2)
|
||||
print(ParameterizedSingletonMeta.get_all_instances())
|
||||
Reference in New Issue
Block a user