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

NetSuite沙箱RESTlet API无法获取订单数据求助

NetSuite沙箱环境RESTlet返回空数组排查方案

我搭建的RESTlet API在生产环境能正常从NetSuite的「已保存交易搜索」获取订单数据,但在沙箱环境调用时仅返回空数组[]。已重新配置集成、检查权限及所有ID,仍未定位问题。

Python订单数据获取代码

import time
import uuid
import urllib.parse
import hmac
import hashlib
import base64
import requests

def generate_oauth_signature(method, url, consumer_key, consumer_secret, token_id, token_secret, realm=""):
    params = {
        'oauth_consumer_key': consumer_key,
        'oauth_nonce': uuid.uuid4().hex,
        'oauth_signature_method': 'HMAC-SHA256',
        'oauth_timestamp': str(int(time.time())),
        'oauth_token': token_id,
        'oauth_version': '1.0',
    }

    url_parts = urllib.parse.urlparse(url)
    query_params = urllib.parse.parse_qs(url_parts.query)
    for key, value in query_params.items():
        params[key] = value[0]

    sorted_params = sorted(params.items())

    base_string = method.upper() + '&' + urllib.parse.quote_plus(url_parts.scheme + '://' + url_parts.netloc + url_parts.path) + '&' + urllib.parse.quote_plus('&'.join([urllib.parse.quote_plus(k) + '=' + urllib.parse.quote_plus(v) for k, v in sorted_params]))

    signing_key = urllib.parse.quote_plus(consumer_secret) + '&' + urllib.parse.quote_plus(token_secret)

    signature = hmac.new(signing_key.encode(), base_string.encode(), hashlib.sha256).digest()
    oauth_signature = base64.b64encode(signature).decode()

    auth_header = 'OAuth realm="' + realm + '", '
    auth_header += 'oauth_consumer_key="' + urllib.parse.quote_plus(consumer_key) + '", '
    auth_header += 'oauth_token="' + urllib.parse.quote_plus(token_id) + '", '
    auth_header += 'oauth_signature_method="HMAC-SHA256", '
    auth_header += 'oauth_timestamp="' + params['oauth_timestamp'] + '", '
    auth_header += 'oauth_nonce="' + params['oauth_nonce'] + '", '
    auth_header += 'oauth_version="1.0", '
    auth_header += 'oauth_signature="' + urllib.parse.quote_plus(oauth_signature) + '"'

    return auth_header

def fetch_netsuite_orders():
    #This is for production
    '''  
    netsuite_url = "https://11111111.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=111&deploy=1"
    consumer_key = "11111111"
    consumer_secret = "11111111"
    token_id = "11111111"
    token_secret = "111111111"
    realm = "11111111"
    '''

    #This is for sandbox
    netsuite_url = "https://11111111-sb1.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=111&deploy=1"
    consumer_key = "11111111"
    consumer_secret = "11111111"
    token_id = "11111111"
    token_secret = "11111111"
    realm = "11111111_SB1"
    
    auth_header = generate_oauth_signature("GET", netsuite_url, consumer_key, consumer_secret, token_id, token_secret, realm)
    #print(auth_header)

    headers = {
        'Authorization': auth_header,
        'Content-Type': 'application/json',
    }

    response = requests.get(netsuite_url, headers=headers)

    if response.status_code == 200:
        #print("Success")
        #print(response.json())
        return response.json()
    else:
        print(f"Failed to fetch orders, status code: {response.status_code}")
        print(response.text)
        return []
        

def fetch_orders():
    orders_data = fetch_netsuite_orders()
    return orders_data


if __name__ == "__main__":
    orders_data = fetch_orders()
    print(orders_data)

NetSuite RESTlet API代码

/**
 * @NApiVersion 2.x
 * @NScriptType Restlet
 */

define(['N/search'], function(search) {

    function doGet(requestParams) {
        var searchId = 'customsearch696000';
        var searchResults = [];

        var mySearch = search.load({
            id: searchId
        });

        var resultSet = mySearch.run();
        var searchIndex = 0;
        var result;

        do {
            result = resultSet.getRange({
                start: searchIndex,
                end: searchIndex + 1000
            });

            if (result && result.length > 0) {
                searchResults = searchResults.concat(result);
                searchIndex += result.length;
            }
        } while (result && result.length == 1000);

        return searchResults;
    }

    return {
        'get': doGet
    };
});

排查步骤

  • 确认沙箱中customsearch696000已保存搜索的有效性:直接在沙箱NetSuite后台打开该搜索,手动执行查看是否有结果。沙箱数据是生产快照,若后续未同步新数据或搜索条件在沙箱环境下无匹配记录,会返回空结果。
  • 检查RESTlet部署权限:沙箱中部署该RESTlet时,确认授权角色拥有该已保存搜索的访问权限,以及搜索涉及交易记录的查看权限。
  • 验证OAuth凭证准确性:确认沙箱的realm格式正确(通常为账号_SB1),consumer key、token等均为沙箱专属集成凭证,而非生产环境的凭证。
  • 添加RESTlet调试日志:修改doGet函数加入日志,排查是搜索加载失败还是无数据返回,示例代码如下:
    function doGet(requestParams) {
        var searchId = 'customsearch696000';
        var searchResults = [];
        try {
            var mySearch = search.load({
                id: searchId
            });
            log.debug('搜索加载成功', 'ID: ' + searchId);
            
            var resultSet = mySearch.run();
            var searchIndex = 0;
            var result;
    
            do {
                result = resultSet.getRange({
                    start: searchIndex,
                    end: searchIndex + 1000
                });
                log.debug('批次结果数量', '起始位置: ' + searchIndex + ', 数量: ' + (result ? result.length : 0));
                
                if (result && result.length > 0) {
                    searchResults = searchResults.concat(result);
                    searchIndex += result.length;
                }
            } while (result && result.length == 1000);
            
            log.debug('总结果数量', searchResults.length);
            return searchResults;
        } catch(e) {
            log.error('错误信息', e.message);
            return {error: e.message};
        }
    }
    
  • 核对RESTlet URL参数:确认沙箱URL中的script和deploy ID与沙箱中实际部署的脚本ID一致,沙箱脚本ID可能与生产不同(除非是从生产复制的部署)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 08:57:03