Files
sixGuoDjango/wechat_项目工具包/EMS中国邮政/ems邮政.py
T

303 lines
18 KiB
Python
Raw Normal View History

2026-08-02 14:07:04 +08:00
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商家侧支付订单号="")