@memberjunction/esignature 5.40.0 → 5.40.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +414 -0
- package/package.json +6 -6
package/README.md
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
[← Back to eSignature Overview](../README.md)
|
|
2
|
+
|
|
3
|
+
# @memberjunction/esignature
|
|
4
|
+
|
|
5
|
+
The **core primitive** of the MemberJunction eSignature subsystem. This package defines the provider-agnostic contract every signing vendor implements, the normalized types that describe an envelope's lifecycle, and the engines that orchestrate sending, status tracking, document persistence, and auditing.
|
|
6
|
+
|
|
7
|
+
Provider driver packages (DocuSign, PandaDoc, Dropbox Sign) depend on this package; they implement its contract. Your application depends on this package's engine; it never talks to a vendor directly.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @memberjunction/esignature
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Two entry points
|
|
16
|
+
|
|
17
|
+
This package ships **two** importable surfaces, deliberately separated so client bundles stay free of server-only dependencies:
|
|
18
|
+
|
|
19
|
+
```mermaid
|
|
20
|
+
graph TB
|
|
21
|
+
subgraph root["@memberjunction/esignature (browser-safe)"]
|
|
22
|
+
Types["types<br/><i>normalized contracts</i>"]
|
|
23
|
+
BSP["BaseSignatureProvider<br/><i>the driver contract</i>"]
|
|
24
|
+
SEB["SignatureEngineBase<br/><i>metadata cache</i>"]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
subgraph server["@memberjunction/esignature/server (server-only)"]
|
|
28
|
+
SE["SignatureEngine<br/><i>lifecycle + persistence</i>"]
|
|
29
|
+
Util["driver init + credentials"]
|
|
30
|
+
Art["artifact file-back"]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
SE --> SEB
|
|
34
|
+
SE --> BSP
|
|
35
|
+
Util --> Cred["@memberjunction/credentials"]
|
|
36
|
+
Art --> Storage["@memberjunction/storage"]
|
|
37
|
+
|
|
38
|
+
style root fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
39
|
+
style server fill:#8a5a2d,stroke:#5c3a1a,color:#fff
|
|
40
|
+
style Cred fill:#444,stroke:#222,color:#fff
|
|
41
|
+
style Storage fill:#444,stroke:#222,color:#fff
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
| Import | Contains | Safe in browser? |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `@memberjunction/esignature` | Types, `BaseSignatureProvider`, `SignatureEngineBase` | ✅ Yes |
|
|
47
|
+
| `@memberjunction/esignature/server` | `SignatureEngine`, driver-init utilities, artifact file-back | ❌ Server only (depends on `@memberjunction/credentials`) |
|
|
48
|
+
|
|
49
|
+
> **Why the split?** The server engine decrypts credentials and writes to the database — it pulls in `@memberjunction/credentials`, which has no place in a browser bundle. The root entry gives UI code everything it needs (the contract, the types, and a read-only metadata cache) without that weight.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## The provider contract: `BaseSignatureProvider`
|
|
54
|
+
|
|
55
|
+
Every signing vendor is wrapped in a driver that extends this abstract class and registers itself with the MJ class factory:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
@RegisterClass(BaseSignatureProvider, 'DocuSign')
|
|
59
|
+
export class DocuSignSignatureProvider extends BaseSignatureProvider { … }
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The engine never imports a driver by name — it resolves one at runtime from the `ServerDriverKey` on the provider record. Add a vendor by publishing a new driver package; the engine picks it up with zero changes.
|
|
63
|
+
|
|
64
|
+
### Operations
|
|
65
|
+
|
|
66
|
+
```mermaid
|
|
67
|
+
classDiagram
|
|
68
|
+
class BaseSignatureProvider {
|
|
69
|
+
<<abstract>>
|
|
70
|
+
+initialize(config) Promise
|
|
71
|
+
+IsConfigured bool
|
|
72
|
+
+CreateEnvelope(req)* EnvelopeResult
|
|
73
|
+
+GetEnvelopeStatus(id)* EnvelopeStatusResult
|
|
74
|
+
+DownloadSignedDocument(id)* SignedDocumentResult
|
|
75
|
+
+VoidEnvelope(id, reason)* OperationResult
|
|
76
|
+
+CreateEmbeddedSigningUrl(req) SigningUrlResult
|
|
77
|
+
+ApplyTemplate(req) EnvelopeResult
|
|
78
|
+
+ResendNotification(id) OperationResult
|
|
79
|
+
+ParseWebhookEvent(payload, headers) NormalizedSignatureEvent
|
|
80
|
+
+VerifyWebhookSignature(body, headers) WebhookVerificationResult
|
|
81
|
+
+getSupportedOperations()* SignatureOperation[]
|
|
82
|
+
+supportsOperation(op) bool
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
| Operation | Required? | Purpose |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| `CreateEnvelope` | **Required** | Send one or more documents to recipients for signature. |
|
|
89
|
+
| `GetEnvelopeStatus` | **Required** | Poll the provider for the current envelope + recipient statuses. |
|
|
90
|
+
| `DownloadSignedDocument` | **Required** | Retrieve the completed, signed PDF bytes. |
|
|
91
|
+
| `VoidEnvelope` | **Required** | Cancel an in-flight envelope with a reason. |
|
|
92
|
+
| `CreateEmbeddedSigningUrl` | Optional | Generate an in-app signing URL for embedded signing flows. |
|
|
93
|
+
| `ApplyTemplate` | Optional | Create an envelope from a provider-hosted template. |
|
|
94
|
+
| `ResendNotification` | Optional | Re-send the signing email to pending recipients. |
|
|
95
|
+
| `ParseWebhookEvent` | Optional | Translate a provider webhook payload into a normalized event. |
|
|
96
|
+
| `VerifyWebhookSignature` | Optional | Confirm an inbound webhook genuinely came from the provider. |
|
|
97
|
+
|
|
98
|
+
Optional operations have safe default implementations that return a clear "not supported" result — a driver only overrides what its vendor actually offers. Callers check `supportsOperation(...)` or `getSupportedOperations()` before relying on an optional feature.
|
|
99
|
+
|
|
100
|
+
> Implementing a new provider? See [Adding a provider](#adding-a-new-provider) below, and the existing drivers for reference: [DocuSign](../Providers/DocuSign/README.md) · [PandaDoc](../Providers/PandaDoc/README.md) · [Dropbox Sign](../Providers/DropboxSign/README.md).
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Normalized types
|
|
105
|
+
|
|
106
|
+
The contract speaks one vocabulary regardless of vendor. The most important shapes:
|
|
107
|
+
|
|
108
|
+
### Status
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
type EnvelopeStatus =
|
|
112
|
+
| 'Draft' | 'Sent' | 'Delivered' | 'Signed'
|
|
113
|
+
| 'Completed' | 'Declined' | 'Voided' | 'Unknown';
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Each driver maps its vendor's native statuses onto this set, so application code branches on one stable enumeration.
|
|
117
|
+
|
|
118
|
+
### Requests & results (abridged)
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
interface CreateEnvelopeRequest {
|
|
122
|
+
title: string;
|
|
123
|
+
message?: string;
|
|
124
|
+
documents: SignatureDocumentInput[]; // { bytes, filename, contentType }
|
|
125
|
+
recipients: SignatureRecipientInput[]; // { email, name?, routingOrder?, role? }
|
|
126
|
+
sendImmediately?: boolean;
|
|
127
|
+
metadata?: Record<string, unknown>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
interface EnvelopeResult {
|
|
131
|
+
Success: boolean;
|
|
132
|
+
externalEnvelopeId?: string;
|
|
133
|
+
status?: EnvelopeStatus;
|
|
134
|
+
signingUrl?: string;
|
|
135
|
+
ErrorMessage?: string;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
interface NormalizedSignatureEvent {
|
|
139
|
+
externalEnvelopeId: string;
|
|
140
|
+
status: EnvelopeStatus;
|
|
141
|
+
occurredAt: string; // ISO 8601 timestamp
|
|
142
|
+
raw: unknown;
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Results follow the MemberJunction convention of a `Success` boolean plus an optional `ErrorMessage` — never thrown exceptions for expected outcomes.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## The engines
|
|
151
|
+
|
|
152
|
+
### `SignatureEngine` — server-side orchestration
|
|
153
|
+
|
|
154
|
+
The heart of the subsystem. It resolves accounts to drivers, decrypts credentials, executes provider operations, and persists the entire lifecycle. Imported from the `/server` subpath:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { SignatureEngine } from '@memberjunction/esignature/server';
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```mermaid
|
|
161
|
+
flowchart TD
|
|
162
|
+
Send["SendForSignature()"] --> Resolve["Resolve account → provider → driver"]
|
|
163
|
+
Resolve --> Decrypt["Decrypt credentials<br/>(Credential vault)"]
|
|
164
|
+
Decrypt --> Init["driver.initialize(mergedConfig)"]
|
|
165
|
+
Init --> Create["driver.CreateEnvelope()"]
|
|
166
|
+
Create --> Persist["Create Request + Documents +<br/>Recipients + Log rows"]
|
|
167
|
+
Persist --> Return["Return result"]
|
|
168
|
+
|
|
169
|
+
style Send fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
170
|
+
style Decrypt fill:#8a5a2d,stroke:#5c3a1a,color:#fff
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
| Method | What it does |
|
|
174
|
+
|---|---|
|
|
175
|
+
| `SendForSignature(options, user)` | Resolve the account, initialize the driver, create the envelope, and persist the Request + Documents + Recipients + an audit Log row. Optionally links the request to an originating record via `entityId`/`recordId`, and can send documents *by reference* to an existing Artifact version. |
|
|
176
|
+
| `RefreshStatus(requestId, user)` | Poll the provider for the latest status, update the Request + Recipients, and log the transition. |
|
|
177
|
+
| `DownloadSigned(requestId, user)` | Fetch the signed PDF; if a storage account is configured, file it back as a new Artifact version and record it as a **Signed** document. |
|
|
178
|
+
| `Void(requestId, reason, user)` | Cancel the envelope at the provider and mark the Request **Voided**. |
|
|
179
|
+
| `RecordWebhookEvent(driverKey, payload, headers, user, rawBody?)` | Verify and apply an inbound provider webhook (see [Webhooks](#inbound-webhooks)). |
|
|
180
|
+
| `GetDriver(accountId, user)` | Resolve and return a fully-initialized driver for advanced/direct use. |
|
|
181
|
+
|
|
182
|
+
### `SignatureEngineBase` — browser-safe metadata cache
|
|
183
|
+
|
|
184
|
+
A `BaseEngine` subclass (like `FileStorageEngineBase`) that caches the **Providers** and **Accounts** metadata for read-only use — perfect for populating a UI dropdown of available accounts. It does **not** decrypt credentials or touch a vendor.
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { SignatureEngineBase } from '@memberjunction/esignature';
|
|
188
|
+
|
|
189
|
+
await SignatureEngineBase.Instance.Config(false, contextUser);
|
|
190
|
+
const accounts = SignatureEngineBase.Instance.Accounts;
|
|
191
|
+
const account = SignatureEngineBase.Instance.GetAccountByName('Production DocuSign');
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
| Accessor | Returns |
|
|
195
|
+
|---|---|
|
|
196
|
+
| `Accounts` | All configured Signature Accounts. |
|
|
197
|
+
| `Providers` | All registered Signature Providers. |
|
|
198
|
+
| `AccountsWithProviders` | Accounts joined to their provider for convenient display. |
|
|
199
|
+
| `GetAccountById` / `GetAccountByName` | A single account lookup. |
|
|
200
|
+
| `GetProviderById` / `GetProviderByDriverKey` | A single provider lookup. |
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Data model
|
|
205
|
+
|
|
206
|
+
Six entities back the subsystem. Field-level detail:
|
|
207
|
+
|
|
208
|
+
### MJ: Signature Providers
|
|
209
|
+
Registry of provider *types*. Seeded via metadata (`metadata/signature-providers/`), not SQL.
|
|
210
|
+
|
|
211
|
+
| Field | Type | Notes |
|
|
212
|
+
|---|---|---|
|
|
213
|
+
| `Name` | string | Display name, e.g. "DocuSign". Unique. |
|
|
214
|
+
| `ServerDriverKey` | string | Resolves the driver at runtime — must match the `@RegisterClass` key. |
|
|
215
|
+
| `IsActive` | bool | Inactive providers are skipped. |
|
|
216
|
+
| `Priority` | int | Selection order; lower = higher priority. |
|
|
217
|
+
| `RequiresOAuth` | bool | OAuth-based vs. static API key. |
|
|
218
|
+
| `SupportsTemplates` | bool | Capability flag. |
|
|
219
|
+
| `SupportsEmbeddedSigning` | bool | Capability flag. |
|
|
220
|
+
| `Configuration` | JSON | Non-secret provider defaults (e.g. `oauthBase`, `restBase`). |
|
|
221
|
+
|
|
222
|
+
### MJ: Signature Accounts
|
|
223
|
+
A configured *instance* of a provider.
|
|
224
|
+
|
|
225
|
+
| Field | Type | Notes |
|
|
226
|
+
|---|---|---|
|
|
227
|
+
| `Name` | string | e.g. "Production DocuSign". |
|
|
228
|
+
| `SignatureProviderID` | guid → Provider | Which provider type. |
|
|
229
|
+
| `CredentialID` | guid → Credential | Encrypted vendor secrets. |
|
|
230
|
+
| `CompanyID` | guid | Optional tenant/company scope. |
|
|
231
|
+
| `IsActive` / `IsDefault` | bool | Active flag; default-account flag. |
|
|
232
|
+
| `DefaultFromName` / `DefaultFromEmail` | string | Default sender identity. |
|
|
233
|
+
| `Configuration` | JSON | Per-account overrides, merged over provider defaults. |
|
|
234
|
+
|
|
235
|
+
### MJ: Signature Requests
|
|
236
|
+
The envelope. Links to an originating record via the polymorphic pair.
|
|
237
|
+
|
|
238
|
+
| Field | Type | Notes |
|
|
239
|
+
|---|---|---|
|
|
240
|
+
| `SignatureAccountID` | guid → Account | Sent through this account. |
|
|
241
|
+
| `Name` | string | Title / email subject. |
|
|
242
|
+
| `Message` | text | Email body. |
|
|
243
|
+
| `Status` | string | Normalized `EnvelopeStatus`, defaults `Draft`. |
|
|
244
|
+
| `ExternalEnvelopeID` | string | The vendor's envelope identifier. |
|
|
245
|
+
| `EntityID` / `RecordID` | guid / string | **Polymorphic link** to your domain record. |
|
|
246
|
+
| `SentAt` / `CompletedAt` | datetimeoffset | Lifecycle timestamps. |
|
|
247
|
+
| `VoidReason` | string | Set when voided. |
|
|
248
|
+
|
|
249
|
+
### MJ: Signature Request Documents
|
|
250
|
+
| Field | Type | Notes |
|
|
251
|
+
|---|---|---|
|
|
252
|
+
| `SignatureRequestID` | guid → Request | Parent envelope. |
|
|
253
|
+
| `ArtifactID` / `ArtifactVersionID` | guid | Source or signed artifact provenance. |
|
|
254
|
+
| `Name` | string | Filename. |
|
|
255
|
+
| `Sequence` | int | Document order in the envelope; defaults `1`. |
|
|
256
|
+
| `Role` | string | `Source` (sent) or `Signed` (received back). |
|
|
257
|
+
|
|
258
|
+
### MJ: Signature Request Recipients
|
|
259
|
+
| Field | Type | Notes |
|
|
260
|
+
|---|---|---|
|
|
261
|
+
| `SignatureRequestID` | guid → Request | Parent envelope. |
|
|
262
|
+
| `Email` / `Name` | string | Signer identity. |
|
|
263
|
+
| `RoutingOrder` | int | Signing order; defaults `1`. |
|
|
264
|
+
| `Role` | string | Template role (optional). |
|
|
265
|
+
| `Status` | string | Per-recipient status, defaults `Created`. |
|
|
266
|
+
| `SignedAt` | datetimeoffset | When this signer completed. |
|
|
267
|
+
| `ExternalRecipientID` | string | Vendor's recipient identifier. |
|
|
268
|
+
|
|
269
|
+
### MJ: Signature Request Logs
|
|
270
|
+
| Field | Type | Notes |
|
|
271
|
+
|---|---|---|
|
|
272
|
+
| `SignatureRequestID` | guid → Request | Nullable (webhooks for unknown envelopes still log). |
|
|
273
|
+
| `Operation` | string | `CreateEnvelope`, `GetStatus`, `Webhook`, … |
|
|
274
|
+
| `Success` | bool | Outcome. |
|
|
275
|
+
| `StatusBefore` / `StatusAfter` | string | The transition. |
|
|
276
|
+
| `Detail` | text | Full detail / error. |
|
|
277
|
+
|
|
278
|
+
> All six get the standard `__mj_CreatedAt` / `__mj_UpdatedAt` columns and full CRUD stored procedures from CodeGen.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Usage
|
|
283
|
+
|
|
284
|
+
### Using the Actions (no-code)
|
|
285
|
+
|
|
286
|
+
The simplest path. Four Actions (in `@memberjunction/core-actions`) wrap the engine for AI agents and workflow builders — no TypeScript required:
|
|
287
|
+
|
|
288
|
+
| Action | Key inputs | Key outputs |
|
|
289
|
+
|---|---|---|
|
|
290
|
+
| **Send Document for Signature** | `SignatureAccountID`, `Title`, `Documents` *(or `ArtifactVersionID` / `ArtifactID`)*, `Recipients`, `Message?`, `EntityID?`/`RecordID?`, `SendImmediately?`, `Metadata?` | `SignatureRequestID`, `ExternalEnvelopeID`, `Status` |
|
|
291
|
+
| **Get Signature Status** | `SignatureRequestID` | `Status` |
|
|
292
|
+
| **Download Signed Document** | `SignatureRequestID` | `DocumentBase64`, `Filename`, `ContentType` |
|
|
293
|
+
| **Void Signature Request** | `SignatureRequestID`, `Reason` | `Status` (`Voided`) |
|
|
294
|
+
|
|
295
|
+
### Using the engine (server-side code)
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
import { SignatureEngine } from '@memberjunction/esignature/server';
|
|
299
|
+
|
|
300
|
+
// 1. Send — from raw bytes
|
|
301
|
+
const sent = await SignatureEngine.Instance.SendForSignature({
|
|
302
|
+
signatureAccountId,
|
|
303
|
+
title: 'Service Agreement',
|
|
304
|
+
message: 'Please review and sign.',
|
|
305
|
+
documents: [{ bytes: pdf, filename: 'agreement.pdf', contentType: 'application/pdf' }],
|
|
306
|
+
recipients: [{ email: 'alice@acme.com', name: 'Alice Smith', routingOrder: 1 }],
|
|
307
|
+
entityId, recordId, // link to your domain record
|
|
308
|
+
contextUser, // passed inside the options object
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// 2. Check status later (these methods take contextUser as a positional argument)
|
|
312
|
+
const status = await SignatureEngine.Instance.RefreshStatus(sent.signatureRequestId, contextUser);
|
|
313
|
+
|
|
314
|
+
// 3. Download the signed copy (filed back to storage automatically)
|
|
315
|
+
const signed = await SignatureEngine.Instance.DownloadSigned(sent.signatureRequestId, contextUser);
|
|
316
|
+
|
|
317
|
+
// 4. Or cancel
|
|
318
|
+
await SignatureEngine.Instance.Void(sent.signatureRequestId, 'Superseded by amendment', contextUser);
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Browser-safe metadata access
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
import { SignatureEngineBase } from '@memberjunction/esignature';
|
|
325
|
+
|
|
326
|
+
await SignatureEngineBase.Instance.Config(false, contextUser);
|
|
327
|
+
const options = SignatureEngineBase.Instance.AccountsWithProviders; // for a UI picker
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Credential handling
|
|
333
|
+
|
|
334
|
+
Vendor secrets never live in code or config files — they're stored encrypted in the MJ Credential vault and resolved just in time.
|
|
335
|
+
|
|
336
|
+
```mermaid
|
|
337
|
+
sequenceDiagram
|
|
338
|
+
participant Eng as SignatureEngine
|
|
339
|
+
participant Acct as Signature Account
|
|
340
|
+
participant Vault as Credential Vault
|
|
341
|
+
participant Drv as Driver
|
|
342
|
+
|
|
343
|
+
Eng->>Acct: Look up account
|
|
344
|
+
Eng->>Vault: Decrypt CredentialID (subsystem "eSignature")
|
|
345
|
+
Vault-->>Eng: { apiKey / oauth keys / tokens }
|
|
346
|
+
Eng->>Eng: Merge provider defaults + account config + secrets
|
|
347
|
+
Eng->>Drv: initialize(mergedConfig)
|
|
348
|
+
Note over Drv,Vault: On OAuth token rotation,<br/>driver calls onTokenRefresh →<br/>engine persists new tokens to vault
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Configuration is **layered**: provider-type defaults (non-secret) → per-account overrides → decrypted credential values (highest precedence). OAuth drivers can hand rotated tokens back to the engine via an `onTokenRefresh` callback, which persists them so the next call uses fresh tokens.
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## Inbound webhooks
|
|
356
|
+
|
|
357
|
+
Signing vendors push status changes to MemberJunction at `POST /esignature/webhook/:driverKey`. The endpoint (in MJ Server) is intentionally **unauthenticated by MJ** — trust comes from the provider's own signature, verified over the raw request bytes. The policy is **verify-if-configured**: a configured-but-invalid signature is logged and *not* applied; a missing secret is accepted with a warning.
|
|
358
|
+
|
|
359
|
+
```mermaid
|
|
360
|
+
flowchart TD
|
|
361
|
+
In["POST /esignature/webhook/:driverKey"] --> Parse["Bare driver parses payload<br/>(no credentials needed)"]
|
|
362
|
+
Parse --> Find{"Owning Signature Request<br/>found by envelope ID?"}
|
|
363
|
+
Find -->|no| Accept202["202 — received, not actioned<br/>(logged)"]
|
|
364
|
+
Find -->|yes| Verify{"HMAC over raw bytes"}
|
|
365
|
+
Verify -->|secret set & mismatch| Mismatch["202 — verification failed<br/>logged, status NOT applied"]
|
|
366
|
+
Verify -->|verified| Apply["200 — update status + log"]
|
|
367
|
+
Verify -->|no secret configured| ApplyWarn["200 — apply + warn"]
|
|
368
|
+
|
|
369
|
+
style Mismatch fill:#8a2d2d,stroke:#5c1a1a,color:#fff
|
|
370
|
+
style Apply fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
371
|
+
style ApplyWarn fill:#8a5a2d,stroke:#5c3a1a,color:#fff
|
|
372
|
+
style Accept202 fill:#8a5a2d,stroke:#5c3a1a,color:#fff
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
- **Invalid signature** (a secret is configured but the HMAC doesn't match): the failure is logged, the envelope status is **left unchanged**, and the endpoint returns **202** — accepted-but-not-actioned, so the provider doesn't hammer the endpoint with retries of a payload MJ will never trust.
|
|
376
|
+
- **No secret configured**: the event is applied with a warning logged — convenient for development, with a nudge to configure a secret for production.
|
|
377
|
+
- **Verified** event: status applied and logged, **200**.
|
|
378
|
+
- **Unknown envelope**: **202** — accepted (so the provider stops retrying) but not actioned.
|
|
379
|
+
- Malformed requests fail fast: missing driver key → **400**; no system user → **503**; unexpected error → **500**.
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## Adding a new provider
|
|
384
|
+
|
|
385
|
+
1. Create a package depending on `@memberjunction/esignature`.
|
|
386
|
+
2. Subclass `BaseSignatureProvider`, implement the four required operations (plus any optional ones your vendor supports), and map the vendor's statuses onto `EnvelopeStatus`.
|
|
387
|
+
3. Register it: `@RegisterClass(BaseSignatureProvider, 'YourVendorKey')`.
|
|
388
|
+
4. Add a **MJ: Signature Providers** metadata row whose `ServerDriverKey` matches that key.
|
|
389
|
+
5. Export the driver from your package's `index.ts` so importing the package triggers registration.
|
|
390
|
+
|
|
391
|
+
The engine resolves your driver by key the first time an account that uses it sends a document — no engine changes, no wiring.
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## Testing
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
cd packages/eSignature/Base && npm run test
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Unit tests cover the `BaseSignatureProvider` contract — that optional operations default to "not supported", that capability discovery is accurate, and that the base class behaves correctly against mock drivers.
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## Related
|
|
406
|
+
|
|
407
|
+
| Package | Relationship |
|
|
408
|
+
|---|---|
|
|
409
|
+
| [DocuSign driver](../Providers/DocuSign/README.md) | Reference provider — full feature set. |
|
|
410
|
+
| [PandaDoc driver](../Providers/PandaDoc/README.md) | API-key provider — core operations. |
|
|
411
|
+
| [Dropbox Sign driver](../Providers/DropboxSign/README.md) | API-key provider — core + webhooks. |
|
|
412
|
+
| [`@memberjunction/credentials`](../../Credentials) | Encrypted credential storage. |
|
|
413
|
+
| [`@memberjunction/storage`](../../MJStorage) | Artifact file-back for signed documents. |
|
|
414
|
+
| [`@memberjunction/core-actions`](../../Actions/CoreActions) | The four no-code Actions. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/esignature",
|
|
3
|
-
"version": "5.40.
|
|
3
|
+
"version": "5.40.2",
|
|
4
4
|
"description": "MemberJunction eSignature primitive — pluggable provider contract and normalized types for sending documents for electronic signature (DocuSign, Adobe Sign, …). The optional server engine is exposed via the '@memberjunction/esignature/server' subpath.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
"url": "https://github.com/MemberJunction/MJ"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@memberjunction/global": "5.40.
|
|
42
|
-
"@memberjunction/core": "5.40.
|
|
43
|
-
"@memberjunction/core-entities": "5.40.
|
|
44
|
-
"@memberjunction/credentials": "5.40.
|
|
45
|
-
"@memberjunction/storage": "5.40.
|
|
41
|
+
"@memberjunction/global": "5.40.2",
|
|
42
|
+
"@memberjunction/core": "5.40.2",
|
|
43
|
+
"@memberjunction/core-entities": "5.40.2",
|
|
44
|
+
"@memberjunction/credentials": "5.40.2",
|
|
45
|
+
"@memberjunction/storage": "5.40.2"
|
|
46
46
|
}
|
|
47
47
|
}
|