Skip to content

feat: implement DocuSeal real e-signature provider (issue #23) - #28

Merged
pope-h merged 1 commit into
Shelterflex:mainfrom
robertocarlous:feat/real-esignature-provider-issue-23
Aug 18, 2026
Merged

feat: implement DocuSeal real e-signature provider (issue #23)#28
pope-h merged 1 commit into
Shelterflex:mainfrom
robertocarlous:feat/real-esignature-provider-issue-23

Conversation

@robertocarlous

Copy link
Copy Markdown
Contributor

Summary

Replaces the in-memory stub e-signature provider with a production-ready DocuSeal integration for lease agreement signing workflows. The stub remains the default for local development.

Closes #23


What changed

New files

  • src/services/docusealESignatureProvider.ts — Full DocuSeal provider implementing the existing ESignatureProvider interface (4 methods):

    • createSigningRequest() — Creates a DocuSeal template submission via their API
    • getSigningUrl() — Returns a DocuSeal-hosted signing URL for a given signer
    • handleWebhook() — Parses DocuSeal webhook payloads and updates request state
    • verifySignature() — Checks if a specific signer has completed signing
    • Webhook HMAC-SHA256 signature verification (verifyDocusealWebhookSignature())
    • Hybrid persistence layer (in-memory + Postgres) following the same pattern as leaseAgreementStore
  • src/services/docusealESignatureProvider.test.ts — 23 unit tests covering all provider methods, webhook signature verification, document-hash binding, and edge cases (invalid signatures, expired tokens, non-completion events)

  • migrations/047_esign_requests.sql — New esign_requests table for tracking signing requests across restarts:

    • request_id (PK), document_key, document_hash, signers (JSONB), status, provider_id, signer_states (JSONB), created_at, updated_at

Modified files

  • src/services/eSignatureService.ts — Factory function changed from sync to async createESignatureProvider() with dynamic import() for the docuseal case (tree-shaking friendly)

  • src/routes/leaseAgreements.ts — Replaced module-level eager provider instantiation with lazy-init async helper getEsignProvider(). All 3 usage sites updated (send, sign-url, webhook routes)

  • src/config/featureFlags.ts — Updated LEASE_AGREEMENTS_ENABLED comment to reflect real provider is now available

  • .env.example — Added DocuSeal configuration section with ESIGN_PROVIDER, DOCUSEAL_API_URL, DOCUSEAL_API_KEY, DOCUSEAL_WEBHOOK_SECRET

  • docs/openapi.yml — Added 8 lease endpoint paths, LeaseAgreement schema definition, and Lease Agreements tag


Design decisions

  1. No interface changes — The ESignatureProvider interface (4 methods) is the stable seam. DocuSeal plugs into it without modifying any existing call sites.

  2. Stub remains defaultESIGN_PROVIDER defaults to stub. Set ESIGN_PROVIDER=docuseal with real credentials for staging/production. LEASE_AGREEMENTS_ENABLED stays false by default.

  3. Lazy provider initialization — The route now uses an async getEsignProvider() helper instead of eagerly creating the provider at module load. This ensures the dynamic import works correctly and environment variables are read at call time.

  4. Hybrid persistence — DocuSeal requests are stored in-memory (fast reads) with Postgres fallback for durability across restarts. Follows the same hybrid pattern already established in leaseAgreementStore.

  5. Env vars read at call timegetDocusealApiUrl(), getDocusealApiKey(), getDocusealWebhookSecret() are getter functions, not module-level constants. This allows test stubs to inject values via process.env without fighting module caching.

  6. Webhook signature verification — DocuSeal signs payloads with HMAC-SHA256. The provider strips the signature field from the payload before verifying, matching DocuSeal's documented behavior.

  7. Document-hash binding — Each SigningRequest carries a documentHash derived from the lease documentKey via SHA-256. This ties the signing request to a specific document version, preventing tampering.


Environment variables

Variable Required Default Description
ESIGN_PROVIDER No stub Set to docuseal to enable real provider
DOCUSEAL_API_URL Yes (if docuseal) DocuSeal API base URL
DOCUSEAL_API_KEY Yes (if docuseal) DocuSeal API key
DOCUSEAL_WEBHOOK_SECRET No HMAC secret for webhook verification (skips verification if not set)

Test results

lint             — clean
openapi:validate — clean (only pre-existing warnings)
test:ci          — 199 files passed, 2285 tests passed

- Add DocuSeal provider with all 4 interface methods
- Add hybrid persistence layer (in-memory + Postgres)
- Add webhook HMAC-SHA256 signature verification
- Add migration 047 for esign_requests table
- Update factory to async with dynamic import for docuseal
- Update lease agreements route with lazy-init provider
- Add OpenAPI specs for lease agreement endpoints
- Add 23 tests for DocuSeal provider (all passing)
@pope-h
pope-h merged commit eb76c23 into Shelterflex:main Aug 18, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement a real e-signature provider for lease agreements (all signing runs on an in-memory stub)

2 participants