尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

cal.diy 中的 Salesforce 集成指南:把预订参与者自动写入 Sales Cloud 联系人

cal.diy 中的 Salesforce 集成指南:把预订参与者自动写入 Sales Cloud 联系人 cal.diy 中的 Salesforce 集成指南把预订参与者自动写入 Sales Cloud 联系人【免费下载链接】cal.diyScheduling infrastructure for absolutely everyone.项目地址: https://gitcode.com/GitHub_Trending/ca/cal.diy本篇技术指南以本仓库cal.diyCal.com 调度基础设施项目中 Salesforce 集成应用的官方描述文档 DESCRIPTION.md 为核心结合其 OAuth 授权、CRM 服务、事件类型配置与 Salesforce Apex 扩展包的完整源码实现系统讲解该集成把活动参与者attendees自动创建为 Salesforce 联系人的核心能力、可配置项与开发发布流程。读完本文你将掌握该集成的安装授权链路、事件类型级配置项语义、底层 CRM 服务实现原理以及基于 scratch org 的本地开发与 SFDC Unlocked Package 发布方法。集成总览Salesforce (Sales Cloud) 是什么、集成做什么按照官方描述文档 DESCRIPTION.md 的定义Salesforce (Sales Cloud) 是一款云端应用它通过集中管理客户信息、记录客户与公司的交互、并自动化销售人员每天要完成的许多任务来帮助销售团队更聪明、更快速地销售。在该集成中Cal.com 承担的角色是调度基础设施当客户通过你的 Cal.com 链接完成预订后集成会把本次预订的参与者自动同步到 Salesforce作为 Sales Cloud 中的联系人Contact记录从而让销售数据与日程数据打通。其特性在文档中被明确概括为一条将预订事件参与者event attendees创建为 Salesforce (Sales Cloud) 中的联系人。从 config.json 可以看到该应用在 app-store 体系中的完整元信息{ name: Salesforce, slug: salesforce, type: salesforce_crm, logo: icon.png, variant: crm, categories: [crm], extendsFeature: EventType, publisher: Cal.com, Inc., isOAuth: true }关键点解读type: salesforce_crm表明它实现了 Cal.com 的 CRM 集成契约注册在 CRM 类型下variant: crm与categories: [crm]决定它在应用市场中归属于 CRM 分类extendsFeature: EventType说明该应用会为每个事件类型EventType提供可独立配置的能力卡片AppCardisOAuth: true说明其安装走 OAuth 授权流程。从项目整体结构看这个集成由两部分组成一部分是 Cal.com 侧基于 Node/TypeScript 的集成服务位于 packages/app-store/salesforce另一部分是部署在 Salesforce 组织内的 Apex 扩展包sfdc-package两者通过 OAuth 凭据与 Named Credential 协同工作。OAuth 授权链路从安装到令牌落地Salesforce 集成的安装是一个标准 OAuth 2.0 授权码流程由两个 Next.js API 端点完成发起授权api/add.ts与回调换令牌api/callback.ts。发起授权api/add.ts 中服务端先从应用密钥中读取consumer_key缺失时直接返回400 Salesforce client id missingconst appKeys await getAppKeysFromSlug(salesforce); if (typeof appKeys.consumer_key string) consumerKey appKeys.consumer_key; if (!consumerKey) return res.status(400).json({ message: Salesforce client id missing. });随后基于 jsforce 构造 OAuth2 客户端并以refresh_token full作为授权 scope前者换取长期可用令牌后者申请完整 API 权限const salesforceClient new jsforce.Connection({ oauth2: { clientId: consumerKey, redirectUri: ${WEBAPP_URL_FOR_OAUTH}/api/integrations/salesforce/callback, }, }); const url salesforceClient.oauth2.getAuthorizationUrl({ scope: refresh_token full, ...(state { state }), }); res.status(200).json({ url });注意redirectUri指向WEBAPP_URL_FOR_OAUTH下的 callback 端点同时会把请求的 OAuth state 编码进授权 URL用于回调时校验会话归属。回调换令牌与令牌生命周期探测api/callback.ts 在拿到code后使用consumer_key与consumer_secret调用 Salesforce 令牌端点换取访问令牌const conn new jsforce.Connection({ oauth2: { clientId: consumerKey, clientSecret: consumerSecret, redirectUri: ... }, }); const salesforceTokenInfo await conn.oauth2.requestToken(code as string);值得注意的实现细节是集成并不会直接保存令牌而是先调用 Salesforce 的 OIDC Token Introspection 端点lib/getSalesforceTokenLifetime.ts计算令牌有效时长再把token_lifetime一并存入凭据const response await fetch(${instanceUrl}/services/oauth2/introspect, { method: POST, headers: { Authorization: Basic ${Buffer.from(${consumer_key}:${consumer_secret}).toString(base64)}, Content-Type: application/x-www-form-urlencoded, }, body: new URLSearchParams({ token: accessToken, token_type_hint: access_token }), }); const tokenLifetime data.exp - data.iat; // exp 与 iat 均以秒为单位最终通过createOAuthAppCredential将{ ...salesforceTokenInfo, token_lifetime }落库然后重定向回应用安装页getInstalledAppPath。凭据对象的结构在 lib/CrmService.ts 中有对应的 zod schema 约束包含instance_url、access_token、scope、token_lifetime等字段。事件类型级配置项schema 定义与界面开关得益于extendsFeature: EventType每个事件类型都可以独立开关并配置 Salesforce 同步行为。配置结构定义在 zod.ts 的appDataSchema中基于eventTypeAppCardZod扩展其配置 UI 实现在 components/EventTypeAppCardInterface.tsx。核心记录类型与写回枚举lib/enums.ts 定义了所有配置项可用的枚举值Salesforce 记录类型SalesforceRecordEnumContact、Lead、Account、Event写回时机WhenToWriteToRecordevery_booking每次预订都写、field_empty仅当字段为空时写字段类型SalesforceFieldTypedate、string、phone、custom、picklist、boolean、datetime、textarea日期数据来源DateFieldTypeDatabooking_start_date、booking_created_date、booking_cancel_date轮询路由判定来源RoutingReasonsaccount_lookup_field、lead_owner、contact_owner、account_owner。完整配置项清单appDataSchema中可配置项及语义如下布尔开关均有对应的 UI 控件字符串/记录型字段在配置卡片中编辑配置键类型默认值语义roundRobinLeadSkipboolean无轮询路由Round Robin分配时跳过已有匹配记录的参与者roundRobinSkipCheckRecordOnContact/Lead/AccountContact轮询跳过时在哪种记录上检查归属rrSkipFieldRules{field, value, action}[]无字段级跳过规则action为ignore命中则跳过或must_include必须命中才跳过ifFreeEmailDomainSkipOwnerCheckbooleanfalse免费邮箱域名如 gmail.com跳过 owner 检查roundRobinSkipFallbackToLeadOwnerbooleanfalse找不到归属时回退到 Lead OwnerskipContactCreationboolean无不自动创建联系人createEventOnContact/Lead/AccountContact在哪种记录类型下创建 Salesforce EventcreateNewContactUnderAccountboolean无在 Account 下新建 ContactcreateLeadIfAccountNullboolean无Account 为空时创建 LeadonBookingWriteToEventObjectbooleanfalse预订成功后写回 Event 对象onBookingWriteToEventObjectMaprecord{}Event 对象字段映射createEventOnLeadCheckForContactboolean无以 Lead 创建 Event 前先检查 ContactonBookingChangeRecordOwnerbooleanfalse预订后修改记录 OwneronBookingChangeRecordOwnerNamestring[]新 Owner 名称sendNoShowAttendeeDatabooleanfalse发送 No-Show 参与者数据sendNoShowAttendeeDataFieldstringNo-Show 数据写入的字段onBookingWriteToRecordbooleanfalse预订成功后写回记录onBookingWriteToRecordFieldsrecord{}记录字段映射值为writeToBookingEntryignoreGuestsbooleanfalse忽略参与者Guests只处理主预订人onCancelWriteToEventRecordbooleanfalse取消预订时写回onCancelWriteToEventRecordFieldsrecord{}取消时字段映射其中写回条目的结构writeToBookingEntry为{ value: string | boolean, // 要写入的值 fieldType: SalesforceFieldType, // 对应 SF 字段类型 whenToWrite: WhenToWriteToRecord // every_booking | field_empty }UI 侧components/EventTypeAppCardInterface.tsx通过useAppContextWithSchema读写这些配置并渲染三个记录类型下拉框recordOptionsContact/Lead/Account决定createEventOn与checkOwnerOptionsContact/Lead/Account决定roundRobinSkipCheckRecordOn以及WriteToObjectSettings、FieldRulesSettings两个子设置组件见 components/components。核心实现CRM 服务如何把参与者写进 Salesforce整个集成的主逻辑集中在 lib/CrmService.ts约 1956 行它实现了 Cal.com 通用的 CRM 接口并通过 lib/index.ts 以BuildCrmService的名义导出供 crmManager 统一调度。SalesforceCRMService的关键设计lib/CrmService.ts持有appOptions即上一节的事件类型配置预订流程按配置逐项执行基于jsforce维护惰性Connection用存储的令牌访问 Salesforce REST API通过 graphql/SalesforceGraphQLClient.ts 使用 Salesforce GraphQL 端点schema 定义见 src/gql查询记录归属额外扩展了SalesforceCRM接口增加 Salesforce 特有的三个方法findUserEmailFromLookupField按查找字段反查用户邮箱与路由来源、incompleteBookingWriteToRecord预订信息不完整时的写回兜底、getAllPossibleAccountWebsiteFromEmailDomain从邮箱域名推断 Account 官网。联系人查找与去重是核心流程之一代码中定义了SalesforceDuplicateError类型lib/CrmService.ts用于解析 Salesforce 重复规则Duplicate Rule返回的matchResults从而在写入前判断目标联系人是否已存在。工具函数 lib/utils/getDominantAccountId.ts 与 lib/utils/getAllPossibleWebsiteValuesFromEmailDomain.ts 负责在多候选 Account 之间决策归属、从免费邮箱域名推导可能官网这些均有配套单元测试lib/utils/tests。对轮询路由Round Robin场景集成还引入了Assignment Reason记录配置打开roundRobinLeadSkip后服务会基于rrSkipFieldRules与RoutingReasons判定跳过逻辑并通过 lib/repositories/PrismaAssignmentReasonRepository.ts 把路由原因持久化便于审计。集成测试lib/tests/CrmService.integration.test.ts配合 salesforceMock.ts 与 graphql/tests中的 urqlMock 覆盖了创建联系人、写入 Event、GraphQL 查询等关键链路是理解预期行为的最佳参考。Salesforce 侧 Apex 扩展包与用户同步集成并不止于 Cal.com 单向写数据还包括部署在 Salesforce 组织内的 Unlocked Packagesfdc-package实现Salesforce 变化反向通知 Cal.com的双向能力。Apex 源码位于 sfdc-package/force-app/main/defaultCalComCalloutQueueable.cls以 Queueable 方式异步向 Cal.com 发起 Callout避免在触发器同步链路中阻塞事务UserUpdateHandler.cls 与 UserUpdateTrigger.trigger监听 Salesforce 侧 User 记录变更触发同步请求CalComHttpMock.cls测试用的 HTTP Mock配合 UserUpdateHandlerTest.cls 与 CalComCalloutQueueableTest.cls 验证行为Named CredentialCalCom_Development.namedCredential-meta.xml 与CalCom_Production用于把目标 Cal.com 实例地址配置化开发环境指向本地实例生产环境指向线上。Cal.com 侧接收这些同步请求的端点是 api/user-sync.ts。该 POST 端点会校验请求体中的instanceUrl是否与已存储凭据匹配、orgId是否与凭据 URL 中的组织 ID 一致、以及邮箱与团队用户是否对应三重校验全部通过才返回{ success: true }防止跨组织伪造同步请求。本地开发、测试与包发布流程官方开发文档 README.md 给出了完整的工程化流程。创建 Salesforce 测试组织Scratch Org需先安装 Salesforce CLI然后yarn scratch-org:create # 按 project-scratch-def.json 配置创建 scratch org yarn scratch-org:start # 在浏览器中打开该 org若要在本地联调需要把 scratch org 中的 Named Credential 指向本地实例将CalCom_Development指向 localhost。GraphQL 类型生成该集成通过 GraphQL Codegen 从 Salesforce schema 生成查询与类型文档见 README.md由于 Salesforce GraphQL 端点 v63 在生成Setup__JoinInput类型时存在已知报错schema 需从 Salesforce GraphQL introspection 结果转换而来使用graphql-introspection-json-to-sdl工具运行yarn generate:schema生成 SDL 文件开发期间保持yarn codegen:watch后台运行自动从 SDL 生成查询与类型相关配置见 codegen.ts、graphql.config.ts 与 graphqlrc.config.ts。Apex 包部署与发布Apex 侧开发需 Salesforce CLIyarn sfdc:deploy:preview # 预览将部署到 scratch org 的变更 yarn sfdc:deploy # 实际部署到 scratch org发布 Unlocked Package 时所有命令需在 sfdc-package 目录下执行。首次创建包一次性sf package create \ --name calcom-sfdc-package \ --package-type Unlocked \ --path force-app \ --target-dev-hub teamcal.com每次发布新版本sf package version create \ --package calcom-sfdc-package \ --installation-key-bypass \ --wait 20 \ --target-dev-hub teamcal.com其中--installation-key-bypass允许免密码安装--wait 20表示最多等待 20 分钟构建完成准备 promote 时需追加--code-coverage要求 Apex 测试覆盖率不低于 75%。查看已发布版本sf package version list --target-dev-hub teamcal.com安装 URL 格式为https://login.salesforce.com/packaging/installPackage.apexp?p004t_SUBSCRIBER_PACKAGE_VERSION_ID。Beta 版只能安装到沙盒/scratch org允许安装到生产组织的版本需先 promotesf package version promote \ --package calcom-sfdc-packageX.X.X-X \ --target-dev-hub teamcal.com运行 Apex 测试与覆盖率统计sf project deploy start --target-org org-alias sf apex run test --test-level RunLocalTests --wait 10 --target-org org-alias小结本文基于官方描述文档 DESCRIPTION.md 展开完整梳理了 cal.diy 中 Salesforce 集成将预订参与者创建为 Sales Cloud 联系人这一核心能力的技术全貌OAuth 授权与令牌生命周期管理api/add.ts、api/callback.ts、二十余项事件类型级配置zod.ts、以 lib/CrmService.ts 为核心的写入与去重逻辑、Salesforce 侧 Apex 包的双向同步以及从 scratch org 到 Unlocked Package 发布的一整套工程实践。如需深入源码建议从 lib/tests/CrmService.integration.test.ts 的集成测试与 sfdc-package 的 Apex 触发器入手逐条对照配置项与断言即可快速掌握端到端的数据流。【免费下载链接】cal.diyScheduling infrastructure for absolutely everyone.项目地址: https://gitcode.com/GitHub_Trending/ca/cal.diy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表