如何使用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
相关产品推荐
相关产品推荐

