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物流公司订单编号 = "EMS01KZBJBPHFR9E924DKDW86CGJT_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")