@xenterprises/fastify-xsignwell 1.0.0

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/.gitlab-ci.yml ADDED
@@ -0,0 +1,45 @@
1
+ # ============================================================================
2
+ # GitLab CI/CD Pipeline - xSignwell
3
+ # ============================================================================
4
+ # Runs tests on merge requests and commits to main/master
5
+
6
+ stages:
7
+ - test
8
+
9
+ variables:
10
+ NODE_ENV: test
11
+
12
+ # ============================================================================
13
+ # Shared Configuration
14
+ # ============================================================================
15
+ .shared_rules: &shared_rules
16
+ rules:
17
+ - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
18
+ - if: '$CI_COMMIT_BRANCH == "main"'
19
+ - if: '$CI_COMMIT_BRANCH == "master"'
20
+ - if: '$CI_COMMIT_TAG'
21
+
22
+ # ============================================================================
23
+ # STAGE: TEST
24
+ # ============================================================================
25
+ test:
26
+ stage: test
27
+ image: node:20-alpine
28
+ <<: *shared_rules
29
+
30
+ cache:
31
+ key: ${CI_COMMIT_REF_SLUG}
32
+ paths:
33
+ - node_modules/
34
+
35
+ before_script:
36
+ - npm ci
37
+
38
+ script:
39
+ - echo "Running xSignwell tests..."
40
+ - npm test
41
+ - npm audit --audit-level=high || true
42
+
43
+ retry:
44
+ max: 2
45
+ when: runner_system_failure
package/README.md ADDED
@@ -0,0 +1,340 @@
1
+ # xSignwell
2
+
3
+ A Fastify plugin for integrating with SignWell's e-signature API. Provides document creation, template management, bulk sending, and webhook handling.
4
+
5
+ ## Features
6
+
7
+ - **Document Management**: Create, send, and track documents for signing
8
+ - **Template Support**: Create and manage reusable document templates
9
+ - **Embedded Signing**: Get URLs for embedded signing experiences
10
+ - **Bulk Send**: Send documents to multiple recipients at once
11
+ - **Webhooks**: Register and manage webhook callbacks for document events
12
+ - **Test Mode**: Built-in support for SignWell's test mode
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install xsignwell
18
+ ```
19
+
20
+ ## Configuration
21
+
22
+ ### Environment Variables
23
+
24
+ | Variable | Description | Required |
25
+ |----------|-------------|----------|
26
+ | `SIGNWELL_API_KEY` | SignWell API key | Yes |
27
+ | `SIGNWELL_TEST_MODE` | Enable test mode | No |
28
+
29
+ ### Plugin Registration
30
+
31
+ ```javascript
32
+ import Fastify from 'fastify';
33
+ import xSignwell from 'xsignwell';
34
+
35
+ const fastify = Fastify();
36
+
37
+ await fastify.register(xSignwell, {
38
+ apiKey: process.env.SIGNWELL_API_KEY,
39
+ testMode: process.env.NODE_ENV !== 'production',
40
+ });
41
+ ```
42
+
43
+ ## Usage
44
+
45
+ ### Documents
46
+
47
+ #### Create a Document
48
+
49
+ ```javascript
50
+ const document = await fastify.xsignwell.documents.create({
51
+ name: 'Employment Agreement',
52
+ recipients: [
53
+ {
54
+ id: 'signer_1',
55
+ name: 'John Doe',
56
+ email: 'john@example.com',
57
+ },
58
+ ],
59
+ files: [
60
+ {
61
+ name: 'agreement.pdf',
62
+ url: 'https://example.com/agreement.pdf',
63
+ // OR use base64:
64
+ // base64: 'JVBERi0xLjQK...',
65
+ },
66
+ ],
67
+ subject: 'Please sign your employment agreement',
68
+ message: 'Hi John, please review and sign the attached agreement.',
69
+ });
70
+ ```
71
+
72
+ #### Create from Template
73
+
74
+ ```javascript
75
+ const document = await fastify.xsignwell.documents.createFromTemplate(
76
+ 'template_id_here',
77
+ {
78
+ recipients: [
79
+ {
80
+ id: 'signer_1', // Must match template recipient ID
81
+ name: 'John Doe',
82
+ email: 'john@example.com',
83
+ },
84
+ ],
85
+ fields: {
86
+ employee_name: 'John Doe',
87
+ start_date: '2025-02-01',
88
+ salary: '$75,000',
89
+ },
90
+ }
91
+ );
92
+ ```
93
+
94
+ #### Get Document Status
95
+
96
+ ```javascript
97
+ const document = await fastify.xsignwell.documents.get('document_id');
98
+ console.log(document.status); // 'pending', 'completed', etc.
99
+ ```
100
+
101
+ #### Send a Document
102
+
103
+ ```javascript
104
+ await fastify.xsignwell.documents.send('document_id');
105
+ ```
106
+
107
+ #### Send Reminder
108
+
109
+ ```javascript
110
+ await fastify.xsignwell.documents.remind('document_id');
111
+ ```
112
+
113
+ #### Get Completed PDF
114
+
115
+ ```javascript
116
+ const pdf = await fastify.xsignwell.documents.getCompletedPdf('document_id');
117
+ // pdf.file_url contains the download URL
118
+ ```
119
+
120
+ #### Get Embedded Signing URL
121
+
122
+ ```javascript
123
+ const { url, recipient } = await fastify.xsignwell.documents.getEmbeddedSigningUrl(
124
+ 'document_id',
125
+ 'recipient_id'
126
+ );
127
+ // Redirect user to `url` for signing
128
+ ```
129
+
130
+ #### Delete Document
131
+
132
+ ```javascript
133
+ await fastify.xsignwell.documents.remove('document_id');
134
+ ```
135
+
136
+ ### Templates
137
+
138
+ #### Get Template
139
+
140
+ ```javascript
141
+ const template = await fastify.xsignwell.templates.get('template_id');
142
+ ```
143
+
144
+ #### List Templates
145
+
146
+ ```javascript
147
+ const templates = await fastify.xsignwell.templates.list({
148
+ page: 1,
149
+ limit: 20,
150
+ });
151
+ ```
152
+
153
+ #### Create Template
154
+
155
+ ```javascript
156
+ const template = await fastify.xsignwell.templates.create({
157
+ name: 'NDA Template',
158
+ files: [
159
+ {
160
+ name: 'nda.pdf',
161
+ url: 'https://example.com/nda.pdf',
162
+ },
163
+ ],
164
+ recipients: [
165
+ { id: 'signer_1', name: 'Signer' },
166
+ { id: 'signer_2', name: 'Counter-Signer' },
167
+ ],
168
+ });
169
+ ```
170
+
171
+ #### Update Template
172
+
173
+ ```javascript
174
+ await fastify.xsignwell.templates.update('template_id', {
175
+ name: 'Updated NDA Template',
176
+ subject: 'New subject line',
177
+ });
178
+ ```
179
+
180
+ #### Delete Template
181
+
182
+ ```javascript
183
+ await fastify.xsignwell.templates.remove('template_id');
184
+ ```
185
+
186
+ #### Get Template Fields
187
+
188
+ ```javascript
189
+ const fields = await fastify.xsignwell.templates.getFields('template_id');
190
+ ```
191
+
192
+ ### Bulk Send
193
+
194
+ #### Create Bulk Send
195
+
196
+ ```javascript
197
+ const bulkSend = await fastify.xsignwell.bulkSend.create({
198
+ templateId: 'template_id',
199
+ recipients: [
200
+ {
201
+ email: 'john@example.com',
202
+ name: 'John Doe',
203
+ // Field values for this recipient
204
+ employee_name: 'John Doe',
205
+ start_date: '2025-02-01',
206
+ },
207
+ {
208
+ email: 'jane@example.com',
209
+ name: 'Jane Smith',
210
+ employee_name: 'Jane Smith',
211
+ start_date: '2025-02-15',
212
+ },
213
+ ],
214
+ });
215
+ ```
216
+
217
+ #### List Bulk Sends
218
+
219
+ ```javascript
220
+ const bulkSends = await fastify.xsignwell.bulkSend.list();
221
+ ```
222
+
223
+ #### Get Bulk Send Documents
224
+
225
+ ```javascript
226
+ const documents = await fastify.xsignwell.bulkSend.getDocuments('bulk_send_id');
227
+ ```
228
+
229
+ #### Get CSV Template
230
+
231
+ ```javascript
232
+ const csvTemplate = await fastify.xsignwell.bulkSend.getCsvTemplate('template_id');
233
+ ```
234
+
235
+ ### Webhooks
236
+
237
+ #### List Webhooks
238
+
239
+ ```javascript
240
+ const webhooks = await fastify.xsignwell.webhooks.list();
241
+ ```
242
+
243
+ #### Create Webhook
244
+
245
+ ```javascript
246
+ const webhook = await fastify.xsignwell.webhooks.create({
247
+ callbackUrl: 'https://your-api.com/webhooks/signwell',
248
+ event: 'document.completed', // Optional: subscribe to specific event
249
+ });
250
+ ```
251
+
252
+ #### Delete Webhook
253
+
254
+ ```javascript
255
+ await fastify.xsignwell.webhooks.remove('webhook_id');
256
+ ```
257
+
258
+ #### Handle Webhook Events
259
+
260
+ ```javascript
261
+ fastify.post('/webhooks/signwell', async (request, reply) => {
262
+ const event = fastify.xsignwell.webhooks.parseEvent(request.body);
263
+
264
+ switch (event.event) {
265
+ case 'document.completed':
266
+ console.log(`Document ${event.documentId} completed`);
267
+ // Download the completed PDF
268
+ const pdf = await fastify.xsignwell.documents.getCompletedPdf(event.documentId);
269
+ break;
270
+
271
+ case 'document.signed':
272
+ console.log(`Document ${event.documentId} signed by ${event.recipient?.email}`);
273
+ break;
274
+
275
+ case 'document.declined':
276
+ console.log(`Document ${event.documentId} was declined`);
277
+ break;
278
+ }
279
+
280
+ return { received: true };
281
+ });
282
+ ```
283
+
284
+ #### Webhook Event Types
285
+
286
+ ```javascript
287
+ const events = fastify.xsignwell.webhooks.events;
288
+ // {
289
+ // DOCUMENT_COMPLETED: 'document.completed',
290
+ // DOCUMENT_SENT: 'document.sent',
291
+ // DOCUMENT_VIEWED: 'document.viewed',
292
+ // DOCUMENT_SIGNED: 'document.signed',
293
+ // DOCUMENT_DECLINED: 'document.declined',
294
+ // DOCUMENT_EXPIRED: 'document.expired',
295
+ // DOCUMENT_VOIDED: 'document.voided',
296
+ // RECIPIENT_COMPLETED: 'recipient.completed',
297
+ // RECIPIENT_VIEWED: 'recipient.viewed',
298
+ // RECIPIENT_SIGNED: 'recipient.signed',
299
+ // RECIPIENT_DECLINED: 'recipient.declined',
300
+ // }
301
+ ```
302
+
303
+ ### Account Info
304
+
305
+ ```javascript
306
+ const account = await fastify.xsignwell.me();
307
+ console.log(account.email);
308
+ ```
309
+
310
+ ## API Reference
311
+
312
+ ### Decorators
313
+
314
+ After registration, the following decorators are available on the Fastify instance:
315
+
316
+ | Decorator | Description |
317
+ |-----------|-------------|
318
+ | `fastify.xsignwell.documents` | Document management methods |
319
+ | `fastify.xsignwell.templates` | Template management methods |
320
+ | `fastify.xsignwell.bulkSend` | Bulk send methods |
321
+ | `fastify.xsignwell.webhooks` | Webhook management methods |
322
+ | `fastify.xsignwell.me` | Get account info |
323
+ | `fastify.xsignwell.config` | Plugin configuration |
324
+
325
+ ### Rate Limits
326
+
327
+ SignWell API has the following rate limits:
328
+ - Standard requests: 100 requests per 60 seconds
329
+ - Document creation: 30 requests per minute
330
+ - Test mode: 20 requests per minute
331
+
332
+ ## Testing
333
+
334
+ ```bash
335
+ npm test
336
+ ```
337
+
338
+ ## License
339
+
340
+ MIT
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@xenterprises/fastify-xsignwell",
3
+ "version": "1.0.0",
4
+ "description": "Fastify plugin for SignWell document signing API integration",
5
+ "type": "module",
6
+ "main": "src/xSignwell.js",
7
+ "engines": {
8
+ "node": ">=20.0.0",
9
+ "npm": ">=10.0.0"
10
+ },
11
+ "scripts": {
12
+ "test": "node --test test/xSignwell.test.js",
13
+ "lint": "eslint src/ test/"
14
+ },
15
+ "keywords": [
16
+ "fastify",
17
+ "signwell",
18
+ "esignature",
19
+ "document-signing",
20
+ "pdf",
21
+ "api"
22
+ ],
23
+ "author": "X Enterprises",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "fastify-plugin": "^5.0.1"
27
+ },
28
+ "peerDependencies": {
29
+ "fastify": "^5.0.0"
30
+ },
31
+ "devDependencies": {
32
+ "eslint": "^9.0.0",
33
+ "fastify": "^5.0.0"
34
+ }
35
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * SignWell Bulk Send Service
3
+ *
4
+ * Handles bulk document sending operations.
5
+ *
6
+ * @see https://developers.signwell.com/reference/get_api-v1-bulk-sends-id
7
+ */
8
+
9
+ /**
10
+ * Setup bulk send service
11
+ *
12
+ * @param {import('fastify').FastifyInstance} fastify - Fastify instance
13
+ * @param {Object} config - Plugin configuration
14
+ */
15
+ export async function setupBulkSend(fastify, config) {
16
+ const { apiKey, baseUrl, testMode } = config;
17
+
18
+ /**
19
+ * Make an API request to SignWell
20
+ * @param {string} endpoint - API endpoint
21
+ * @param {Object} options - Fetch options
22
+ * @returns {Promise<Object>} API response
23
+ */
24
+ async function apiRequest(endpoint, options = {}) {
25
+ const url = `${baseUrl}${endpoint}`;
26
+ const headers = {
27
+ "X-Api-Key": apiKey,
28
+ "Content-Type": "application/json",
29
+ ...options.headers,
30
+ };
31
+
32
+ const response = await fetch(url, {
33
+ ...options,
34
+ headers,
35
+ });
36
+
37
+ if (!response.ok) {
38
+ const errorText = await response.text();
39
+ let errorData;
40
+ try {
41
+ errorData = JSON.parse(errorText);
42
+ } catch {
43
+ errorData = { message: errorText };
44
+ }
45
+ fastify.log.error({ endpoint, status: response.status, error: errorData }, "SignWell API error");
46
+ throw new Error(errorData.message || `SignWell API error: ${response.status}`);
47
+ }
48
+
49
+ const text = await response.text();
50
+ if (!text) return { success: true };
51
+
52
+ return JSON.parse(text);
53
+ }
54
+
55
+ /**
56
+ * Get a bulk send by ID
57
+ * @param {string} bulkSendId - Bulk send ID
58
+ * @returns {Promise<Object>} Bulk send details
59
+ */
60
+ async function get(bulkSendId) {
61
+ return apiRequest(`/bulk_sends/${bulkSendId}`, {
62
+ method: "GET",
63
+ });
64
+ }
65
+
66
+ /**
67
+ * List all bulk sends
68
+ * @param {Object} [params] - Query parameters
69
+ * @param {number} [params.page] - Page number
70
+ * @param {number} [params.limit] - Items per page
71
+ * @returns {Promise<Object>} List of bulk sends
72
+ */
73
+ async function list(params = {}) {
74
+ const queryParams = new URLSearchParams();
75
+ if (params.page) queryParams.set("page", params.page);
76
+ if (params.limit) queryParams.set("limit", params.limit);
77
+
78
+ const queryString = queryParams.toString();
79
+ const endpoint = `/bulk_sends${queryString ? `?${queryString}` : ""}`;
80
+
81
+ return apiRequest(endpoint, {
82
+ method: "GET",
83
+ });
84
+ }
85
+
86
+ /**
87
+ * Create a bulk send
88
+ * @param {Object} params - Bulk send parameters
89
+ * @param {string} params.templateId - Template ID to use
90
+ * @param {Array<Object>} params.recipients - List of recipient data
91
+ * @param {string} [params.subject] - Email subject
92
+ * @param {string} [params.message] - Email message
93
+ * @returns {Promise<Object>} Created bulk send
94
+ */
95
+ async function create(params) {
96
+ const {
97
+ templateId,
98
+ recipients,
99
+ subject,
100
+ message,
101
+ ...rest
102
+ } = params;
103
+
104
+ const body = {
105
+ document_template_id: templateId,
106
+ bulk_send_rows: recipients,
107
+ test_mode: testMode || params.testMode,
108
+ ...rest,
109
+ };
110
+
111
+ if (subject) body.subject = subject;
112
+ if (message) body.message = message;
113
+
114
+ return apiRequest("/bulk_sends", {
115
+ method: "POST",
116
+ body: JSON.stringify(body),
117
+ });
118
+ }
119
+
120
+ /**
121
+ * Get CSV template for bulk send
122
+ * @param {string} templateId - Template ID
123
+ * @returns {Promise<Object>} CSV template info
124
+ */
125
+ async function getCsvTemplate(templateId) {
126
+ return apiRequest(`/bulk_sends/csv_template?document_template_id=${templateId}`, {
127
+ method: "GET",
128
+ });
129
+ }
130
+
131
+ /**
132
+ * Validate CSV for bulk send
133
+ * @param {string} templateId - Template ID
134
+ * @param {string} csvContent - CSV content (base64 or raw)
135
+ * @returns {Promise<Object>} Validation result
136
+ */
137
+ async function validateCsv(templateId, csvContent) {
138
+ return apiRequest("/bulk_sends/validate_csv", {
139
+ method: "POST",
140
+ body: JSON.stringify({
141
+ document_template_id: templateId,
142
+ csv: csvContent,
143
+ }),
144
+ });
145
+ }
146
+
147
+ /**
148
+ * Get documents from a bulk send
149
+ * @param {string} bulkSendId - Bulk send ID
150
+ * @param {Object} [params] - Query parameters
151
+ * @param {number} [params.page] - Page number
152
+ * @param {number} [params.limit] - Items per page
153
+ * @returns {Promise<Object>} List of documents
154
+ */
155
+ async function getDocuments(bulkSendId, params = {}) {
156
+ const queryParams = new URLSearchParams();
157
+ if (params.page) queryParams.set("page", params.page);
158
+ if (params.limit) queryParams.set("limit", params.limit);
159
+
160
+ const queryString = queryParams.toString();
161
+ const endpoint = `/bulk_sends/${bulkSendId}/documents${queryString ? `?${queryString}` : ""}`;
162
+
163
+ return apiRequest(endpoint, {
164
+ method: "GET",
165
+ });
166
+ }
167
+
168
+ // Add bulk send service to xsignwell namespace
169
+ fastify.xsignwell.bulkSend = {
170
+ get,
171
+ list,
172
+ create,
173
+ getCsvTemplate,
174
+ validateCsv,
175
+ getDocuments,
176
+ };
177
+
178
+ console.info(" • Bulk Send Service initialized");
179
+ }