feat(documenso): full v2 endpoint coverage + sequential signing + redirectUrl

Wire up the remaining version-aware paths so a port pointed at Documenso 2.x
takes the v2 endpoint on every CRUD operation, with two new v2-only settings
exposed in admin UI.

documenso-client.ts:
- createDocument: v2 multipart /envelope/create + getDocument follow-up to
  return the full doc shape (v1 path unchanged)
- sendDocument: v2 /envelope/distribute (returns per-recipient signingUrl in
  the same response — eliminates the v1 separate-GET round-trip)
- sendReminder: v2 /envelope/redistribute with recipientIds filter
- downloadSignedPdf: v2 /envelope/{id}/download
- CreateDocumentMeta type: { subject, message, redirectUrl, signingOrder }
  threaded through v1 + v2 paths (v1 ignores signingOrder)

port-config.ts:
- New settings: documenso_signing_order (PARALLEL/SEQUENTIAL, v2-only),
  documenso_redirect_url (both versions honour)
- PortDocumensoConfig gains signingOrder + redirectUrl

documenso-payload.ts:
- DocumensoTemplatePayload.meta gains signingOrder
- buildDocumensoPayload reads from options.signingOrder, omits when null

document-templates.ts (EOI template flow):
- Pass docCfg.signingOrder + docCfg.redirectUrl into buildDocumensoPayload

documents.service.ts (sendForSigning uploaded-doc flow):
- Pass portId to documensoCreate + documensoSend (was missing)
- Thread signingOrder + redirectUrl via the new meta param

Admin Documenso settings page:
- v2 benefits card updated: now lists envelope CRUD, one-call send,
  sequential enforcement, post-sign redirect as wired (was roadmap)
- Roadmap callout pruned to the three remaining deferred items:
  template/use migration, /envelope/update, non-SIGNER recipient roles
- New "v2 signing behaviour" SettingsFormCard with the two new settings

Template flow stays on /api/v1/templates/{id}/generate-document by design —
Documenso 2.x accepts v1 endpoints via backward compat; full migration to
v2 /template/use requires per-template field-ID capture (admin schema work,
deferred).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-11 14:38:45 +02:00
parent ad312df8a4
commit d597e158fe
8 changed files with 365 additions and 58 deletions

View File

@@ -77,6 +77,16 @@ export const SETTING_KEYS = {
// uses templates rather than per-deal uploads. Optional.
documensoContractTemplateId: 'documenso_contract_template_id',
documensoReservationTemplateId: 'documenso_reservation_template_id',
// v2-only: PARALLEL (default) or SEQUENTIAL signing-order enforcement on
// multi-recipient envelopes. When SEQUENTIAL is set + apiVersion=v2,
// Documenso refuses to email recipient N+1 until recipient N has signed.
// Ignored entirely on v1 instances.
documensoSigningOrder: 'documenso_signing_order',
// v2-only override of the post-signing redirect URL set on documentMeta.
// Falls back to the embedded signing host (or APP_URL) when unset. Use
// this to land signed clients on /portal/eoi-complete (or wherever
// makes sense for the workflow).
documensoRedirectUrl: 'documenso_redirect_url',
// Branding
brandingLogoUrl: 'branding_logo_url',
@@ -222,6 +232,19 @@ export interface PortDocumensoConfig {
* user's email for in-CRM signing-status updates. */
developerUserId: string | null;
approverUserId: string | null;
/**
* v2-only: PARALLEL (default) or SEQUENTIAL signing-order enforcement.
* `null` keeps the upstream default (PARALLEL); a non-null value gets
* passed verbatim. v1 instances ignore this — see admin Documenso page.
*/
signingOrder: 'PARALLEL' | 'SEQUENTIAL' | null;
/**
* v2-only: post-signing redirect URL set on documentMeta. When null,
* the upstream Documenso default applies (Documenso's own thank-you
* page). Typically set to `{embeddedSigningHost}/sign/success` so
* signers land back on the branded marketing site.
*/
redirectUrl: string | null;
}
function toIntOrNull(raw: unknown): number | null {
@@ -255,6 +278,8 @@ export async function getPortDocumensoConfig(portId: string): Promise<PortDocume
approverLabel,
developerUserId,
approverUserId,
signingOrder,
redirectUrlOverride,
] = await Promise.all([
readSetting<string>(SETTING_KEYS.documensoApiUrlOverride, portId),
readSetting<string>(SETTING_KEYS.documensoApiKeyOverride, portId),
@@ -276,6 +301,8 @@ export async function getPortDocumensoConfig(portId: string): Promise<PortDocume
readSetting<string>(SETTING_KEYS.documensoApproverLabel, portId),
readSetting<string>(SETTING_KEYS.documensoDeveloperUserId, portId),
readSetting<string>(SETTING_KEYS.documensoApproverUserId, portId),
readSetting<'PARALLEL' | 'SEQUENTIAL'>(SETTING_KEYS.documensoSigningOrder, portId),
readSetting<string>(SETTING_KEYS.documensoRedirectUrl, portId),
]);
return {
@@ -299,6 +326,8 @@ export async function getPortDocumensoConfig(portId: string): Promise<PortDocume
approverLabel: approverLabel ?? 'Approver',
developerUserId: developerUserId ?? null,
approverUserId: approverUserId ?? null,
signingOrder: signingOrder ?? null,
redirectUrl: redirectUrlOverride ?? null,
};
}