You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js集成Shippo跨接口获取shipment.object_id及报错排查

问题背景
  • 后端基于Node.js开发,当前正在集成Shippo物流服务,已成功实现shippo.shipment.create {...}运单创建逻辑,通过console.log(shipment)可正常获取返回的shipment.object_id
  • 核心需求:开发GET接口供客户选择偏好的配送费率,需要在POST创建运单的接口作用域外获取shipment.object_id,进而查询配送费率返回给客户
  • 已尝试方案:在用户Schema中新增shippingId字段,计划将获取到的shipment.object_id存入MongoDB数据库,对应实现代码如下:
.then(function(shipment) 
{ const shippingId = shipment.object_id; 
const userId =  to.userId; 
const user = await User.findById(userId); 
 if (user) { user.shippingId = shippingId; } 
const updatedUser = await user.save(); 
}
  • 运行上述代码时报错:SyntaxError: Unexpected reserved word
  • 当前编写的Shippo路由完整实现代码如下:
import express from 'express';
import shippo from 'shippo';
import expressAsyncHandler from 'express-async-handler';
const shippoToken = env.process.SHIPPO_TOKEN;
const shippoClient = shippo(shippoToken);

const shippoRouter = express.Router();


shippoRouter.post(
    '/', 
expressAsyncHandler(async (req, res) => {
const from = req.body.address
var addressFrom  = {
    "name": from.name,
    "street1": from.street1,
    "city": from.city,
    "state": from.state,
    "zip": from.zip,
    "country": from.country,
};


const to = req.body.name
var addressTo = {
    "name": to.name,
    "street1": to.street1,
    "street2": to.street2,
    "city": to.city,
    "state": to.state,
    "zip": to.zip,
    "country": to.country,
    "phone": to.phone,
    "email":to.email,
    "validate": true,
};

var parcel = {
    "length": "5",
    "width": "5",
    "height": "5",
    "distance_unit": "in",
    "weight": "1.5",
    "mass_unit": "lb"
};


shippoClient.shipment.create({
    "address_from": addressFrom,
    "address_to": addressTo,
    "parcels": [parcel],
    "async": false
})
.catch(function(err) {
    // Deal with an error
    console.log("There was an error creating customs declaration: %s", err);

}).then(function(shipment) {
    const shippingId = shipment.object_id
console.log(shipment)
console.log(shippingId)
})

}));

shippoRouter.get (
    '/shippingInfo', 
expressAsyncHandler(async (req, res) => {
   // shippo.shipment.retrieve(shipment.object_id);
}));

问题原因与修复方案

报错根因

SyntaxError: Unexpected reserved word 报错原因很直接:await关键字只能在被async标记的函数内部使用,你之前把await写在了.then()的普通function回调里,语法层面就不支持,所以直接抛错。
除了这个语法错误,现有代码还有几个会导致运行异常的问题:

  • 环境变量读取顺序写反:env.process.SHIPPO_TOKEN应该改成process.env.SHIPPO_TOKEN
  • 接口参数取值错误:const to = req.body.name逻辑不对,正常应该从请求体取收件人地址对象,否则后续读to.name等字段会直接报空指针
  • 异步逻辑没有等待:Shippo创建运单是异步IO操作,现在的写法没有等待创建完成就会结束请求,前端拿不到返回结果
  • 链式调用顺序错误:.catch().then()的写法会导致异常捕获不全,正常应该先写.then()再跟.catch(),或者直接用try/catch配合async/await
  • 用户ID取值风险:不要从请求体里传userId,要从登录态中间件解析当前用户ID,避免用户篡改参数操作其他人的数据

跨接口获取shipment.object_id的实现思路

把shipment.object_id持久化存储的思路是对的,但不建议直接存在用户表单字段里——一个用户会产生多个运单,单字段存储会覆盖历史运单ID,最好单独建一个运单集合存储,关联用户ID、运单ID、费率信息、创建时间等字段。
另外注意:创建运单时传了async: false,Shippo会同步返回运单结果,其中自带rates字段包含所有可用配送费率,不需要再额外调用retrieve接口二次查询,直接存库就能用,能减少一次API请求开销。
如果服务是多实例部署,绝对不要用全局内存变量存运单ID,进程重启、请求负载到不同实例都会导致数据丢失,必须存在MongoDB这类持久化存储里。

修正后的核心代码参考

import express from 'express';
import shippo from 'shippo';
import expressAsyncHandler from 'express-async-handler';
// 修正环境变量读取顺序
const shippoToken = process.env.SHIPPO_TOKEN;
const shippoClient = shippo(shippoToken);

const shippoRouter = express.Router();


shippoRouter.post(
    '/', 
    expressAsyncHandler(async (req, res) => {
        // 修正参数取值,根据实际前端传参结构调整
        const from = req.body.addressFrom;
        const to = req.body.addressTo;
        // 从登录态取当前用户ID,避免篡改
        const userId = req.user._id;

        const addressFrom  = {
            "name": from.name,
            "street1": from.street1,
            "city": from.city,
            "state": from.state,
            "zip": from.zip,
            "country": from.country,
        };

        const addressTo = {
            "name": to.name,
            "street1": to.street1,
            "street2": to.street2,
            "city": to.city,
            "state": to.state,
            "zip": to.zip,
            "country": to.country,
            "phone": to.phone,
            "email":to.email,
            "validate": true,
        };

        const parcel = {
            "length": "5",
            "width": "5",
            "height": "5",
            "distance_unit": "in",
            "weight": "1.5",
            "mass_unit": "lb"
        };

        try {
            // 直接await创建结果,避免回调作用域问题
            const shipment = await shippoClient.shipment.create({
                "address_from": addressFrom,
                "address_to": addressTo,
                "parcels": [parcel],
                "async": false
            });
            const shippingId = shipment.object_id;
            
            // 持久化存储运单ID,推荐新建Shipment模型存全量运单信息
            // 临时存在用户表的写法,外层是async函数可直接使用await
            const user = await User.findById(userId);
            if (user) {
                // 多运单场景建议改成数组格式push,不要直接赋值覆盖
                user.shippingId = shippingId;
                await user.save();
            }
            // 直接返回运单ID和可用费率,前端可直接进入选择费率流程
            res.send({
                success: true,
                shippingId: shippingId,
                rates: shipment.rates
            })
        } catch (err) {
            console.log("创建运单失败: %s", err);
            res.status(500).send({success: false, message: "运单创建失败"})
        }
    })
);

shippoRouter.get (
    '/shippingInfo', 
    expressAsyncHandler(async (req, res) => {
        const userId = req.user._id;
        // 从持久化存储取对应的shippingId
        const user = await User.findById(userId);
        if (!user?.shippingId) {
            return res.status(400).send({success: false, message: "未找到对应运单"})
        }
        // 如果创建运单时已经存了rates,这里可以直接查库返回,不用调Shippo接口
        const shipmentInfo = await shippoClient.shipment.retrieve(user.shippingId);
        res.send({
            success: true,
            rates: shipmentInfo.rates
        })
    })
);

export default shippoRouter;

内容的提问来源于stack exchange,提问作者Gigi101

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 23:09:41