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

如何使用SuiteScript实现Salesforce客户同步至NetSuite?

实现Salesforce到NetSuite的客户反向同步方案

核心实现思路

要完成Salesforce客户到NetSuite的反向同步,需要由Salesforce触发同步请求,NetSuite通过RESTlet接收并处理创建/更新逻辑,具体分为以下几个步骤:


1. 配置Salesforce触发机制

在Salesforce中编写Apex触发器,当客户(Customer__c)被创建或编辑时,向NetSuite的RESTlet发送同步请求。触发器需包含:

  • 构造同步所需的客户数据 payload
  • 生成NetSuite TBA(Token-Based Authentication)认证头
  • 发送POST请求到NetSuite RESTlet

示例Apex触发器代码:

trigger SyncCustomerToNetSuite on Customer__c (after insert, after update) {
    // NetSuite RESTlet 地址(替换为你的实际地址)
    String netsuiteRestletUrl = 'https://<你的NetSuite账户ID>.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=<RESTlet脚本ID>&deploy=<部署ID>';
    
    // 从Salesforce Named Credentials获取NetSuite TBA凭证(避免硬编码)
    NamedCredential nc = [SELECT Id, Endpoint, Principal, Credential FROM NamedCredential WHERE DeveloperName = 'NetSuite_TBA'];
    String consumerKey = nc.Principal;
    String consumerSecret = nc.Credential;
    String tokenId = '<你的Token ID>';
    String tokenSecret = '<你的Token Secret>';
    
    // 生成OAuth1.0a认证头(需实现OAuth1Util工具类)
    String authHeader = OAuth1Util.generateAuthHeader(netsuiteRestletUrl, 'POST', consumerKey, consumerSecret, tokenId, tokenSecret);
    
    for (Customer__c cust : Trigger.new) {
        // 构造同步数据
        Map<String, Object> payload = new Map<String, Object>{
            'salesforceId' => cust.Id,
            'action' => Trigger.isInsert ? 'CREATE' : 'UPDATE',
            'companyName' => cust.Name,
            'email' => cust.Email__c,
            'phone' => cust.Phone__c,
            'billingStreet' => cust.BillingStreet__c,
            'billingCity' => cust.BillingCity__c
            // 按需添加其他需要同步的字段
        };
        
        // 发送请求到NetSuite
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        request.setEndpoint(netsuiteRestletUrl);
        request.setMethod('POST');
        request.setHeader('Authorization', authHeader);
        request.setHeader('Content-Type', 'application/json');
        request.setBody(JSON.serialize(payload));
        
        try {
            HttpResponse response = http.send(request);
            if (response.getStatusCode() != 200) {
                System.debug('同步失败,Salesforce ID: ' + cust.Id + ',错误信息: ' + response.getBody());
                // 可选:将失败记录存入自定义对象,用于后续重试
            } else {
                System.debug('同步成功,Salesforce ID: ' + cust.Id);
            }
        } catch (Exception e) {
            System.debug('同步异常: ' + e.getMessage());
        }
    }
}

2. 创建NetSuite RESTlet接收端点

在NetSuite中开发SuiteScript 2.x RESTlet,作为接收Salesforce同步请求的接口,核心逻辑包括:

  • 验证请求参数
  • 根据Salesforce ID查找NetSuite中是否存在对应客户
  • 执行创建或更新操作
  • 返回同步结果

前提准备:在NetSuite客户记录中创建自定义字段custentity_salesforce_id,用于存储Salesforce客户ID,实现双向关联。

示例RESTlet代码:

/**
 * @NApiVersion 2.x
 * @NScriptType Restlet
 * @NModuleScope SameAccount
 */
define(['N/record', 'N/log', 'N/search'], function(record, log, search) {
    function post(context) {
        try {
            log.debug('收到同步请求', JSON.stringify(context));
            
            // 校验必填参数
            if (!context.salesforceId || !context.action || !context.companyName) {
                return { success: false, error: '缺少必填字段:salesforceId、action、companyName' };
            }
            
            var salesforceId = context.salesforceId;
            var action = context.action.toUpperCase();
            var existingCustomerId = findCustomerBySalesforceId(salesforceId);
            
            // 处理创建逻辑
            if (action === 'CREATE') {
                if (existingCustomerId) {
                    return updateNetSuiteCustomer(existingCustomerId, context);
                } else {
                    return createNetSuiteCustomer(context);
                }
            }
            // 处理更新逻辑
            else if (action === 'UPDATE') {
                if (!existingCustomerId) {
                    return { success: false, error: '未找到对应NetSuite客户,Salesforce ID: ' + salesforceId };
                } else {
                    return updateNetSuiteCustomer(existingCustomerId, context);
                }
            } else {
                return { success: false, error: '无效操作类型:' + action };
            }
        } catch (e) {
            log.error('同步错误', e.message);
            return { success: false, error: e.message };
        }
    }
    
    // 根据Salesforce ID查找NetSuite客户
    function findCustomerBySalesforceId(salesforceId) {
        var customerSearch = search.create({
            type: search.Type.CUSTOMER,
            filters: [['custentity_salesforce_id', 'is', salesforceId]],
            columns: ['internalid']
        });
        
        var results = customerSearch.run().getRange({ start: 0, end: 1 });
        return results.length > 0 ? results[0].getValue('internalid') : null;
    }
    
    // 创建NetSuite客户
    function createNetSuiteCustomer(context) {
        var customerRec = record.create({ type: record.Type.CUSTOMER });
        
        // 填充基础字段
        customerRec.setValue({ fieldId: 'companyname', value: context.companyName });
        if (context.email) customerRec.setValue({ fieldId: 'email', value: context.email });
        if (context.phone) customerRec.setValue({ fieldId: 'phone', value: context.phone });
        if (context.billingStreet) customerRec.setValue({ fieldId: 'billaddr1', value: context.billingStreet });
        if (context.billingCity) customerRec.setValue({ fieldId: 'billcity', value: context.billingCity });
        
        // 关联Salesforce ID
        customerRec.setValue({ fieldId: 'custentity_salesforce_id', value: context.salesforceId });
        
        var customerId = customerRec.save({ enableSourcing: true, ignoreMandatoryFields: false });
        log.debug('NetSuite客户创建成功', '客户ID: ' + customerId);
        return { success: true, netsuiteId: customerId };
    }
    
    // 更新NetSuite客户
    function updateNetSuiteCustomer(customerId, context) {
        var customerRec = record.load({ type: record.Type.CUSTOMER, id: customerId });
        
        // 仅更新传入的字段
        if (context.companyName) customerRec.setValue({ fieldId: 'companyname', value: context.companyName });
        if (context.email) customerRec.setValue({ fieldId: 'email', value: context.email });
        if (context.phone) customerRec.setValue({ fieldId: 'phone', value: context.phone });
        if (context.billingStreet) customerRec.setValue({ fieldId: 'billaddr1', value: context.billingStreet });
        if (context.billingCity) customerRec.setValue({ fieldId: 'billcity', value: context.billingCity });
        
        customerRec.save({ enableSourcing: true, ignoreMandatoryFields: false });
        log.debug('NetSuite客户更新成功', '客户ID: ' + customerId);
        return { success: true, netsuiteId: customerId };
    }
    
    return { post: post };
});

3. 关键注意事项

  • 安全认证:使用NetSuite TBA替代硬编码凭证,Salesforce端通过Named Credentials存储敏感信息,避免泄露。
  • 字段映射:梳理双向同步的字段对应关系,确保自定义字段、地址、联系方式等数据准确同步。
  • 冲突处理:定义同步优先级(如以Salesforce为数据源),添加最后修改时间字段,仅同步最新更新的记录,避免双向冲突。
  • 重试机制:在Salesforce中对同步失败的记录进行重试,可通过定时任务或自定义对象存储失败记录。
  • API限额:注意NetSuite和Salesforce的API调用限额,批量同步时需拆分请求,避免触发限流。
  • 日志监控:在两端添加详细日志,便于排查同步异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 07:54:54