Files
sixGuoDjango/wechat_项目工具包/EMS中国邮政/ems邮政.py
T
2026-09-19 11:51:34 +08:00

305 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import time
from datetime import datetime
from typing import Literal, Optional
from zoneinfo import ZoneInfo
import emoji
import orjson
import pydash
import requests
from django.forms import model_to_dict
from pydantic import BaseModel, Field
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 = Field(default="1100244903172", description="发放代金券是创建的批次号")
# ems_delivery_id: str = Field(default="EMS", description="发放代金券是创建的批次号")
ems_biz_id: str = Field(default="test_biz_id", description="ems_biz_id")
ems_delivery_id: str = Field(default="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物流公司订单编号, action_type, action_msg):
""""""
order物流对象 = order订单.objects.get(order物流公司订单编号=order物流公司订单编号)
mini_token = sixToken.get_mini_token()
模拟更新物流状态 = f"https://api.weixin.qq.com/cgi-bin/express/business/test_update_order?access_token={mini_token}"
utc_unix_timestamp = int(datetime.now(tz=ZoneInfo("utc")).timestamp())
模拟数据 = {
"biz_id": self.ems_biz_id,
"order_id": order物流对象.order物流订单号, # 商家生成的订单号
"delivery_id": self.ems_delivery_id, #
"waybill_id": order物流公司订单编号, # 物流订单号
"action_time": utc_unix_timestamp, # 这里是更新发的时间戳
"action_type": action_type,
"action_msg": action_msg,
}
requests.post(url=模拟更新物流状态, data=orjson.dumps(模拟数据, option=orjson.OPT_SORT_KEYS, ).decode(encoding="utf-8"))
def 更新物流快递数据(self, order物流公司订单编号, ):
""""""
查询对象 = order订单.objects.get(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商家侧支付订单号="")
order物流公司订单编号 = "EMS01M2PY16HXDKY9BMQH6XCWDE3D_waybill_id"
ems邮政物流工具().模拟更新订单状态(order物流公司订单编号=order物流公司订单编号, action_type=100001, action_msg="揽件成功")
time.sleep(30)
ems邮政物流工具().模拟更新订单状态(order物流公司订单编号=order物流公司订单编号, action_type=300002, action_msg="开始派送")
time.sleep(30)
# ems邮政物流工具().模拟更新订单状态(order物流公司订单编号=order物流公司订单编号, action_type=300003, action_msg="签收成功")
# ems邮政物流工具().更新物流快递数据(order物流公司订单编号="EMS01KZAD94RDQFQBVVX705YNH6SE_waybill_id")