拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Corsair Loyverse 插件实战指南:59 个端点、OAuth 鉴权与数据镜像机制全解析

Corsair Loyverse 插件实战指南59 个端点、OAuth 鉴权与数据镜像机制全解析【免费下载链接】corsairConnect your users to their apps项目地址: https://gitcode.com/GitHub_Trending/corsa/corsair导读corsair-dev/loyverse是 Corsair 生态中用于连接零售 POS 系统 Loyverse 的官方插件包它把 Loyverse 云端 API 的 59 个操作封装为统一、带风险分级、带本地数据镜像的 Corsair 端点Endpoint并通过 OAuth 2.0 在租户首次使用时自动引导授权。读完本文你将掌握如何安装并挂载该插件、59 个端点的完整清单与风险分级、Loyverse 的 Bearer Token 鉴权机制OAuth 令牌与个人访问令牌为何可以混用、upsert 语义与镜像缓存的设计取舍以及 402/429/404 等各类错误在源码中的精确处理策略。packages/loyverse是 Corsair 官方插件目录packages/中面向零售门店场景的集成包其 README.md 以一张端点总表、鉴权说明与安装命令构成核心骨架。本文以该文档为纲结合 index.ts、client.ts、error-handlers.ts、schema/database.ts 等源码实现把插件从安装到端到端运行的每个环节讲透。安装与快速接入插件以独立 npm 包发布与 Corsair 核心采用 workspace 管理通过 pnpm 安装pnpm add corsair-dev/loyverse包本身是纯 ESM 模块发布产物为dist/index.js及配套类型声明见 package.json 的exports字段并声明了corsair 0.1.0与zod ^4.1.13两个 peerDependency——端点输入/输出全部由 zod 校验因此接入方需要自行安装 zod。安装后即可在应用中构造并挂载插件import { loyverse } from corsair-dev/loyverse; import { corsair } from corsair; const plugin loyverse({ // authType 缺省即为 oauth_2可省略 authType: oauth_2, // 可选直接注入静态 key个人访问令牌跳过 OAuth 引导 // key: your-personal-access-token, }); const app corsair({ plugins: [plugin] });插件构造函数loyverse()位于 index.ts返回的插件对象携带id: loyverse、authConfig、schema、endpoints、endpointMeta、endpointSchemas与errorHandlers等完整元数据可直接被 Corsair 核心识别与调度。LoyversePluginOptions支持authType、key、hooks、webhookHooks、errorHandlers、permissions六项配置其中permissions可基于端点风险级别做细粒度授权详见下文风险分级。鉴权为什么 OAuth 令牌与个人访问令牌可以混用插件 README 明确写着Auth 为 OAuth 2.0Corsair 会在租户首次使用时提示其提供凭据。这一句背后是 index.ts 中一段值得细读的设计注释Loyverse 提供 OAuth 2.0 与个人访问令牌personal access token两种凭据且二者以完全相同的方式呈现——Authorization: Bearer token——因此个人访问令牌与 OAuth 访问令牌在字节层面兼容运行时提供的 key 对两者都适用。这带来两个实际影响没有第二份凭据。不像 Harvest 需要额外的 account id、Zendesk 需要 subdomainLoyverse 仅凭令牌即可唯一标识账户因此loyverseAuthConfig只声明了{ oauth_2: {} }也无需account键或任何解析链resolution chain。key 构建器keyBuilder很简单。见 index.ts若在选项里显式传入key端点调用直接使用它否则在oauth_2模式下从ctx.keys.get_access_token()读取运行时令牌。这也意味着**同一个插件既支持租户 OAuth 引导也支持开发者直接注入个人访问令牌**两种接入方式。在 HTTP 层面client.ts 的buildConfig统一注入Content-Type: application/json与Authorization: Bearer accessToken而两个 OpenID Connect 元数据端点oidc.discovery、oidc.jwks是仅有的例外——它们位于版本化 API 基址之外且无需任何凭据走独立的makeLoyverseMetadataRequestclient.ts。API 基址与一个值得注意的坑版本化 API 基址为https://api.loyverse.com/v1.0版本在路径中而非请求头OpenID Connect 元数据则在同一主机、不带版本前缀的根路径https://api.loyverse.com上提供client.ts。源码注释记录了一个实测发现官方规范文档把 JWKS 写在/oidc/jwks但该 URL 返回 404实际可用的地址是 discovery 文档自己广告的jwks_uri——接入方若手工调用 OIDC 端点应以 discovery 文档返回的值为准。59 个端点全景操作清单与风险分级插件 README 的核心是一张 59 行的端点总表每行给出Operation形如categories.delete的资源.动作命名、Operation ID形如loyverse.api.categories.delete的完全限定标识、Risk 风险级别与描述。这张表与源码中 index.ts 的loyverseEndpointMeta一一对应是 Corsair 权限系统与审计系统的重要输入。风险分级规则风险级别遵循该操作能摧毁什么的原则index.tsread一切只读操作包括全部list/get、discounts.listFiltered、oidc.*与merchant.getwrite所有upsert类写入可覆盖记录但不能删除外加items.uploadImage与inventory.updatedestructive所有delete操作、items.deleteImage以及两个涉及资金流转且无法撤销的收据操作receipts.create与receipts.refund。完整端点清单商品目录CatalogOperationRisk说明items.list/items.getread列出 / 获取单个商品items.upsertwrite创建或更新商品items.deletedestructive删除商品items.uploadImagewrite上传商品图片items.deleteImagedestructive删除商品图片variants.list/variants.getread列出 / 获取商品变体variants.upsertwrite创建或更新变体variants.deletedestructive删除变体categories.list/categories.getread列出 / 获取分类categories.upsertwrite创建或更新分类categories.deletedestructive删除分类modifiers.list/modifiers.getread列出 / 获取修饰项如规格、加料modifiers.upsertwrite创建或更新修饰项modifiers.deletedestructive删除修饰项discounts.listread列出折扣discounts.listFilteredread按 id、时间戳范围或删除状态过滤列出折扣discounts.getread获取单个折扣discounts.upsertwrite创建或更新折扣discounts.deletedestructive删除折扣taxes.list/taxes.getread列出 / 获取税率taxes.upsertwrite创建或更新税率taxes.deletedestructive删除税率客户与供应链OperationRisk说明customers.list/customers.getread列出 / 获取客户customers.upsertwrite创建或更新客户customers.deletedestructive永久删除客户suppliers.list/suppliers.getread列出 / 获取供应商suppliers.upsertwrite创建或更新供应商suppliers.deletedestructive删除供应商门店运营OperationRisk说明posDevices.list/posDevices.getread列出 / 获取 POS 设备posDevices.upsertwrite创建或更新 POS 设备posDevices.deletedestructive删除 POS 设备inventory.listread列出库存水位inventory.updatewrite为商品变体设置库存水位employees.list/employees.getread列出 / 获取员工paymentTypes.list/paymentTypes.getread列出 / 获取支付方式stores.list/stores.getread列出 / 获取门店shifts.listread列出班次收据交易核心OperationRisk说明receipts.list/receipts.getread按收据号列出 / 获取收据receipts.createdestructive记录一笔销售一旦创建不可撤回receipts.refunddestructive退款资金退回客户账户与订阅OperationRisk说明webhooks.list/webhooks.getread列出 / 获取 webhook 订阅webhooks.upsertwrite创建或更新 webhook 订阅webhooks.deletedestructive删除 webhook 订阅merchant.getread获取商户信息oidc.discoveryread获取 OpenID Connect discovery 文档oidc.jwksread获取 JSON Web Key Set从源码结构看这 59 个端点被组织成 17 个资源模块每个模块在 endpoints/ 目录下对应一个文件items.ts、variants.ts、categories.ts、receipts.ts等并在 endpoints/index.ts 以命名空间聚合导出loyverseEndpointsNestedindex.ts将各模块的方法绑定为嵌套路由表同时loyverseEndpointSchemas为每个操作注册了 zod 输入/输出校验模式。upsert为什么没有独立的 create 与 update端点清单里没有单独的创建与更新原因在 index.ts 的注释中写得很清楚Loyverse 对集合 POST 本身就是 upsert 语义——请求体携带id时更新该记录不携带id时创建新记录。因此插件只暴露一个upsert操作并在审计日志中以created: input.id undefined区分两种情形见 endpoints/items.ts。这个语义有一个隐蔽的连带约束以商品items.upsert为例当携带id更新时输入模式强制要求同时提供variants数组因为对 Loyverse 而言缺省 variants会被解读为把变体清空并返回Could not update variants to []见 endpoints/shared.ts 与 endpoints/items.ts。WebhookLoyverse 有订阅 API但插件注册零触发器README 中 Webhooks 一节只有三个单词No webhooks.。这个表述在 index.ts 中有更精确的展开Loyverse 确实发布 webhook——五种事件由下面的webhooks.*操作管理——但本插件不注册任何 Corsair 触发器与 OSS 目录一致其记录 Loyverse 触发器为零。使用个人访问令牌创建的订阅所收到的通知是未签名的因此没有可用于校验投递的凭据。也就是说webhooks.upsert/list/get/delete四个端点只是管理 Loyverse 云端订阅的操作插件本身不消费、不签名校验任何回调投递loyverseWebhooksNested为空对象pluginWebhookMatcher恒返回false。如果你的业务需要监听 Loyverse 的推送事件需要自己在应用层订阅并在回调处做验签设计插件层面不提供触发能力。数据镜像Mirror设计参考数据落库、交易数据不落库插件一个区别于薄客户端的重要设计是把部分实体镜像到本地数据库Corsair 的 schema 存储让后续查询直接命中本地。划分依据记录在 schema/database.ts 与 schema/index.ts镜像13 类实体商品items、变体variants、分类categories、修饰项modifiers、折扣discounts、税率taxes、客户customers、供应商suppliers、门店stores、员工employees、支付方式paymentTypes、POS 设备posDevices、商户merchant——这些参考数据变化频率低且是所有其他操作的查找基础不镜像收据/退款/班次是交易数据持续追加且只有结合日期范围才有意义镜像等于复制一个移动靶库存水位则因根本没有 id 且每次销售都会变化而被排除——一个镜像的库存数字只会误导人。镜像写入是尽力而为best-effort的插件调用不会因为本地副本写入/删除失败而失败endpoints/persist.ts。唯一例外是LoyverseMirrorEvictionError——当记录在 Loyverse 端已被删除、但本地镜像删除失败时抛出详见下文错误处理因为客户数据涉及隐私承诺不能静默吞掉。另外镜像写入采用并发上限 16CACHE_WRITE_CONCURRENCYendpoints/persist.ts一页最多 250 行、商品页还内嵌变体逐行等待会让调用时长远超其背后的那次请求并发则在不淹没数据库的前提下显著提速。两个值得注意的镜像细节均可从源码确认变体随商品级联镜像商品携带内嵌的variants因此items.list/items.get/items.upsert在缓存商品的同时也会缓存其变体endpoints/items.ts不必等一次单独的variants.list删除时级联驱逐删除商品会连带删除其变体所以items.delete在驱逐商品镜像行后会遍历deleted_object_ids一并驱逐对应变体endpoints/items.ts避免留下指向已不存在商品的孤儿行。请求层实现限流、图片上传与公共过滤参数限流配置Loyverse 官方契约是每账户 300 秒 300 次请求超限返回 429RATE_LIMITED。插件在 client.ts 配置了重试策略maxRetries: 3、初始退避 1000ms、退避乘数 2并监听Retry-After响应头。源码注释诚实地记录了该配置的两点边界成功响应不带任何限流响应头客户端无法主动调速只能被动应对 429开发期间无法触发真实 42960 秒内 400 次请求均正常返回因此Retry-After是按文档契约配置的未在真实限流中观测到即使 Loyverse 不返回该头上述退避策略依然生效。商品图片上传裸二进制而非 multipartitems.uploadImage是一个容易踩坑的端点client.ts请求体是原始图片字节raw binary不是multipart/form-data——用 multipart 提交会收到 500INTERNAL_ERROR而裸字节 图片 Content-Type 返回 201 并填充image_url声明类型仅支持image/png与image/jpeg缺省为image/png常量LOYVERSE_IMAGE_MEDIA_TYPE实测中 API 并不严格校验声明类型与字节是否一致但调用方仍应声明正确类型以保持请求规范极小图片如 1x1 PNG也会被 500 拒绝而 64x64 则成功因此收到 500 不一定是调用方错误文档化的类型失败是 415UNSUPPORTED_MEDIA_TYPE。公共列表过滤参数所有集合端点共享一套过滤参数endpoints/types.ts 与 endpoints/shared.ts参数类型说明cursorstring分页游标Loyverse 在最后一页直接省略该键而非返回 null这是没有更多页的信号limitnumber每页条数API 硬上限250超过会返回 400INVALID_VALUE而非静默截断因此模式中直接声明1..250以便在发请求前失败show_deletedboolean是否返回软删除记录软删除记录带deleted_atcreated_at_min/created_at_maxstring创建时间范围updated_at_min/updated_at_maxstring更新时间范围id 过滤通过csv()辅助函数编码为单个逗号分隔参数Loyverse 不接受重复参数空数组会被丢弃而非发送空字符串后者会把结果过滤成零条而不是不过滤。listQuery()还会做compactQuery——剔除值为undefined的键这一点在写路径上尤其关键Loyverse 区分字段缺省与显式 null发送{name: null}会清空该字段省略则保留原值endpoints/shared.ts。另有一处源码注释值得引用参数名拼错会静默失效——Loyverse 对无法识别的查询参数选择忽略而非报错item_ids拼成items_ids的后果是返回整集合200而不是报错因此每个资源专属参数名都经实测确认例如items_ids、variants_ids是复数而modifier_ids、discount_ids、tax_ids是单数无法从资源名推导。错误处理与重试策略非幂等操作绝不重试Loyverse 用标准 HTTP 状态码 一致的{errors:[{code,details,field}]}响应体报告失败error-handlers.ts因此几乎每个处理器只需按状态码匹配。核心设计是幂等性驱动的重试策略非幂等操作集合const NON_IDEMPOTENT_OPERATIONS: ReadonlySetstring new Set([ items.upsert, items.uploadImage, variants.upsert, categories.upsert, modifiers.upsert, discounts.upsert, taxes.upsert, customers.upsert, suppliers.upsert, posDevices.upsert, webhooks.upsert, receipts.create, receipts.refund, ]);Corsair 在处理器请求重试时会重放整个端点调用源码注释指向packages/corsair/core/endpoints/bind.ts的重试逻辑而 Loyverse 不接受任何幂等键所以网络失败发生在服务端已提交写操作之后时重试会造成重复。upsert 是其中的微妙情况带id时重试无害更新同一条不带id时重试会复制出一条新记录——而处理器只看操作名看不到请求体因此一律按不安全处理宁可少一次本来无害的重试也绝不制造重复记录。两个刻意缺席值得一提delete重复执行不构成重复风险第二次会 404由NOT_FOUND_ERROR处理inventory.update虽属写入但不在此列因为其请求体携带stock_after这种绝对值而非增量执行两次库存不变已实测验证。endpoints.test.ts还断言了该集合等于路由表中 POST 操作减去inventory.update防止集合与端点清单漂移。各错误处理器一览处理器触发条件策略RATE_LIMIT_ERROR429 /RATE_LIMITED重试 3 次被拒绝的请求从未生效重放永远安全尊重Retry-AfterSUBSCRIPTION_ERROR402不重试账户套餐不覆盖请求订阅过期或套餐限制如早于 31 天的销售历史重试无济于事AUTH_ERROR401不重试提示检查访问令牌PERMISSION_ERROR403不重试Loyverse 用 403 表示令牌 OAuth 作用域未覆盖该资源个人访问令牌可达全部NOT_FOUND_ERROR404不重试涵盖未知 id 及多数资源的软删除记录VALIDATION_ERROR400 / 415不重试响应体指明出错字段MIRROR_EVICTION_ERROR自定义异常不重试远程已删除而本地镜像删除失败重放只会得到 404SERVER_ERROR5xx仅对幂等操作重试 3 次5xx 前服务端可能已提交写操作NETWORK_ERROR网络类错误仅对幂等操作重试 3 次传输失败无法说明服务端是否已生效DEFAULT兜底不重试记录未处理错误两个实战要点404 ≠ 记录从未存在。软删除行为并非各资源一致error-handlers.ts删除后直接读取items、modifiers、taxes、customers、POS 设备返回 404而categories、suppliers返回 200 并带deleted_at所有资源都会从普通列表中消失、在show_deletedtrue下重现客户删除的 5xx 歧义customers.delete收到 5xx 时服务端可能已提交删除重试会得到 404——重试之所以仍然安全是因为 endpoints/customers.ts 把该 404 当作记录已不存在的确认信号并继续清理本地镜像否则会留下客户个人数据的缓存见 error-handlers.ts 的注释。收据操作交易核心的注意点receipts是全部端点中唯一**不以 UUID 主键、而以顺序收据号如0001**标识的资源endpoints/receipts.ts。其list拥有 API 中最丰富的过滤集按收据号、收据号范围、门店、销售时的order/source、创建/更新时间戳。日期过滤受套餐限制——无 Unlimited sales history 的账户查询 31 天前的收据会收到 402PAYMENT_REQUIREDUnable to retrieve receipts created earlier than 31 days ago而未过滤的列表仍可返回窗口内的数据因此该 402 表示查询越过了套餐上限而非订阅失效。receipts.create每次调用都会产生一张新编号收据、Loyverse 不接受幂等键且每次 POST 恰好接受一笔支付输入模式已约束行级税、折扣与修饰项通过 id 引用已有记录收据级折扣会自动生成对应的行折扣。由于涉及资金且不可撤销receipts.create与receipts.refund被标记为destructive且被排除在集成测试的实网演练之外见 integration.test.ts。测试与验证单元测试、模式测试与实网集成测试插件配备了三层测试可从 package.json 的脚本看出pnpm test # jest运行单元测试与模式测试 pnpm test:live # jest --testPathPatternintegration实网集成测试需令牌 pnpm typecheck # tsc --noEmitclient.test.ts与endpoints.test.ts覆盖客户端构造与端点路由、幂等操作集合等契约后者用于防止NON_IDEMPOTENT_OPERATIONS漂移schema.test.ts断言捕获到的每个响应键都在实体模式中声明防止镜像模式与实际响应脱节schema/database.tsintegration.test.ts针对真实 Loyverse 账户的实网测试运行方式为LOYVERSE_ACCESS_TOKENtoken pnpm test:live。它默认被testPathIgnorePatterns排除、无令牌时自跳过几乎全部为只读调用唯一例外是软删除测试会创建两条自有探针记录再删除收据操作涉及资金与图片上传会改动真实商品被刻意排除在实网演练之外integration.test.ts。总结corsair-dev/loyverse把 Loyverse 的 59 个操作完整封装进了 Corsair 端点体系其核心设计可以概括为四句话鉴权极简Bearer Token 一钥走天下OAuth 访问令牌与个人访问令牌字节兼容无第二凭据、无解析链风险显式化每个操作都带read/write/destructive分级资金类收据操作被明确标为不可撤销镜像有取舍参考数据落库、交易与库存不落库写入尽力而为、仅隐私相关驱逐失败时抛错重试由幂等性驱动Loyverse 不接受幂等键因此所有可能重复的写入在 5xx/网络错误下坚决不重试而 429 则无条件重试。该插件的完整类型定义、端点输入输出 zod 模式与更多示例见 packages/loyverse 目录下的 endpoints/types.ts、schema/database.ts 与三个测试文件接入 Corsair 的框架级用法Express、Next.js、Hono 等可参考仓库的 docs/frameworks 与 docs/adapters 文档。【免费下载链接】corsairConnect your users to their apps项目地址: https://gitcode.com/GitHub_Trending/corsa/corsair创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门