312 lines
19 KiB
Python
312 lines
19 KiB
Python
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: 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物流公司订单编号, 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,
|
||
}
|
||
print("打印 一下模拟的数据,主要就是看一下时间戳的问题", 模拟数据)
|
||
data = requests.post(url=模拟更新物流状态,
|
||
data=orjson.dumps(模拟数据, option=orjson.OPT_SORT_KEYS, ).decode(encoding="utf-8"))
|
||
print("🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 🍊 🍎 🍏 ", data.json())
|
||
|
||
def 查询运单轨迹(self, order物流商家侧订单号):
|
||
""""""
|
||
mini_token = sixToken.get_mini_token()
|
||
|
||
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商家侧支付订单号="")
|
||
ems邮政物流工具().模拟更新订单状态(order物流公司订单编号="EMS01KZAH47JZA3HCY2R5NS0WD3VN_waybill_id", action_type=100001, action_msg="揽件成功")
|
||
time.sleep(30)
|
||
ems邮政物流工具().模拟更新订单状态(order物流公司订单编号="EMS01KZAH47JZA3HCY2R5NS0WD3VN_waybill_id", action_type=300002, action_msg="开始派送")
|
||
time.sleep(30)
|
||
|
||
ems邮政物流工具().模拟更新订单状态(order物流公司订单编号="EMS01KZAH47JZA3HCY2R5NS0WD3VN_waybill_id", action_type=300003, action_msg="签收成功")
|
||
# ems邮政物流工具().更新物流快递数据(order物流公司订单编号="EMS01KZAD94RDQFQBVVX705YNH6SE_waybill_id")
|