From f8c29fa02961d2e9b03d3660221c7484772b0a4b Mon Sep 17 00:00:00 2001 From: Mikhail Date: Mon, 29 Sep 2025 17:05:34 +0300 Subject: [PATCH] Add UploadEmbeddedEditingAndInvite sample - Add Java controller with full functionality matching PHP version - Support for PDF file upload with validation (50MB limit) - Embedded editor link generation - Document invite management and status tracking - Document download functionality - Complete API endpoints for frontend integration - Add comprehensive README documentation - HTML frontend with multi-page workflow --- .../IndexController.java | 571 ++++++++++++++++++ .../UploadEmbeddedEditingAndInvite/README.md | 172 ++++++ .../UploadEmbeddedEditingAndInvite/index.html | 497 +++++++++++++++ 3 files changed, 1240 insertions(+) create mode 100644 src/main/java/com/signnow/samples/UploadEmbeddedEditingAndInvite/IndexController.java create mode 100644 src/main/resources/static/samples/UploadEmbeddedEditingAndInvite/README.md create mode 100644 src/main/resources/static/samples/UploadEmbeddedEditingAndInvite/index.html diff --git a/src/main/java/com/signnow/samples/UploadEmbeddedEditingAndInvite/IndexController.java b/src/main/java/com/signnow/samples/UploadEmbeddedEditingAndInvite/IndexController.java new file mode 100644 index 0000000..985739f --- /dev/null +++ b/src/main/java/com/signnow/samples/UploadEmbeddedEditingAndInvite/IndexController.java @@ -0,0 +1,571 @@ +package com.signnow.samples.UploadEmbeddedEditingAndInvite; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.signnow.Sdk; +import com.signnow.api.document.request.DocumentGetRequest; +import com.signnow.api.document.response.DocumentGetResponse; +import com.signnow.api.document.request.DocumentPostRequest; +import com.signnow.api.document.response.DocumentPostResponse; +import com.signnow.api.document.request.DocumentDownloadGetRequest; +import com.signnow.api.document.response.DocumentDownloadGetResponse; +import com.signnow.api.documentgroup.request.DocumentGroupGetRequest; +import com.signnow.api.documentgroup.request.DocumentGroupPostRequest; +import com.signnow.api.documentgroup.request.DownloadDocumentGroupPostRequest; +import com.signnow.api.documentgroup.request.data.DocumentIdCollection; +import com.signnow.api.documentgroup.response.DocumentGroupGetResponse; +import com.signnow.api.documentgroup.response.DocumentGroupPostResponse; +import com.signnow.api.documentgroup.response.DownloadDocumentGroupPostResponse; +import com.signnow.api.documentgroupinvite.request.GroupInviteGetRequest; +import com.signnow.api.documentgroupinvite.response.GroupInviteGetResponse; +import com.signnow.api.documentgroupinvite.response.data.invite.Action; +import com.signnow.api.documentgroupinvite.response.data.invite.Step; +import com.signnow.api.documentinvite.request.data.To; +import com.signnow.api.documentinvite.request.data.ToCollection; +import com.signnow.api.documentinvite.request.SendInvitePostRequest; +import com.signnow.api.documentgroup.request.DocumentGroupRecipientsGetRequest; +import com.signnow.api.documentgroup.response.DocumentGroupRecipientsGetResponse; +import com.signnow.api.embeddededitor.request.DocumentEmbeddedEditorLinkPostRequest; +import com.signnow.api.embeddededitor.response.DocumentEmbeddedEditorLinkPostResponse; +import com.signnow.core.ApiClient; +import com.signnow.core.exception.SignNowApiException; +import com.signnow.javasampleapp.ExampleInterface; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.MultipartHttpServletRequest; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +@Controller +public class IndexController implements ExampleInterface { + + @Override + public ResponseEntity handleGet(Map queryParams) throws IOException { + // Always return the HTML page and let the client-side JS decide which sub-page to show. + try (var inputStream = getClass().getResourceAsStream("/static/samples/UploadEmbeddedEditingAndInvite/index.html")) { + if (inputStream == null) { + throw new IOException("HTML file not found in classpath"); + } + String html = new String(inputStream.readAllBytes()); + return ResponseEntity.ok().header("Content-Type", "text/html").body(html); + } + } + + @Override + public ResponseEntity handlePost(String formData) throws IOException, SignNowApiException { + @SuppressWarnings("unchecked") + Map data = new ObjectMapper().readValue(formData, Map.class); + String action = (String) data.getOrDefault("action", ""); + + ApiClient client = new Sdk().build().authenticate().getApiClient(); + + switch (action) { + case "upload_and_create_dg": + return uploadAndCreateDocumentGroup(data, client); + case "create_embedded_edit": + return createEmbeddedEditLink(data, client); + case "create_invite": + return createInvite(data, client); + case "invite-status": + return getInviteStatus(data, client); + case "download-document": + return downloadDocument(data, client); + case "get-recipients": + return getDocumentRecipientsApi(data, client); + case "add-recipient": + return addDocumentRecipient(data, client); + case "get-document-roles": + return getDocumentRolesApi(data, client); + default: + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("message", "Invalid action"); + return ResponseEntity.badRequest().body(errorResponse); + } + } + + public ResponseEntity handlePost(MultipartHttpServletRequest request) throws IOException, SignNowApiException { + String action = request.getParameter("action"); + + if (action == null) { + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("message", "Action parameter is required"); + return ResponseEntity.badRequest().body(errorResponse); + } + + ApiClient client = new Sdk().build().authenticate().getApiClient(); + + switch (action) { + case "upload_and_create_dg": + return uploadAndCreateDocumentGroupWithFile(request, client); + default: + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("message", "Invalid action for file upload"); + return ResponseEntity.badRequest().body(errorResponse); + } + } + + private ResponseEntity getDocumentRecipientsApi(Map data, ApiClient client) throws SignNowApiException { + String documentId = (String) data.get("document_id"); + + if (documentId == null || documentId.isEmpty()) { + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("message", "Document ID is required"); + return ResponseEntity.badRequest().body(errorResponse); + } + + List> recipients = getDocumentRecipients(client, documentId); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("recipients", recipients); + return ResponseEntity.ok(response); + } + + private ResponseEntity getDocumentRolesApi(Map data, ApiClient client) throws SignNowApiException { + String documentId = (String) data.get("document_id"); + + if (documentId == null || documentId.isEmpty()) { + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("message", "Document ID is required"); + return ResponseEntity.badRequest().body(errorResponse); + } + + // Get document info + DocumentGetResponse documentGetResponse = getDocument(client, documentId); + List> rolesData = new LinkedList<>(); + + for (var role : documentGetResponse.getRoles()) { + Map roleData = new HashMap<>(); + roleData.put("name", role.getName()); + roleData.put("unique_id", role.getUniqueId()); + roleData.put("signing_order", role.getSigningOrder()); + rolesData.add(roleData); + } + + Map response = new HashMap<>(); + response.put("success", true); + response.put("roles", rolesData); + return ResponseEntity.ok(response); + } + + private ResponseEntity addDocumentRecipient(Map data, ApiClient client) throws SignNowApiException { + String documentId = (String) data.get("document_id"); + String recipientName = (String) data.get("recipient_name"); + String recipientEmail = (String) data.get("recipient_email"); + String recipientRole = (String) data.get("recipient_role"); + + if (documentId == null || documentId.isEmpty() || + recipientName == null || recipientName.isEmpty() || + recipientEmail == null || recipientEmail.isEmpty() || + recipientRole == null || recipientRole.isEmpty()) { + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("message", "All fields are required"); + return ResponseEntity.badRequest().body(errorResponse); + } + + // Get document info + DocumentGetResponse documentGetResponse = getDocument(client, documentId); + var roles = documentGetResponse.getRoles(); + + // Find existing role or create a new one + var targetRole = roles.stream() + .filter(role -> recipientRole.equals(role.getName())) + .findFirst() + .orElse(null); + + if (targetRole == null) { + String availableRoles = roles.stream() + .map(role -> role.getName()) + .reduce((a, b) -> a + ", " + b) + .orElse("none"); + + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("message", "Role '" + recipientRole + "' not found in document. Available roles: " + availableRoles); + return ResponseEntity.badRequest().body(errorResponse); + } + + // Create invite for the recipient + ToCollection to = new ToCollection(); + to.add(new To( + recipientEmail, + targetRole.getUniqueId(), + targetRole.getName(), + Integer.parseInt(targetRole.getSigningOrder()), + "Document Signing Request - Action Required", + "Dear " + recipientName + ", please review and sign the uploaded document." + )); + + // Create and send invite request for document + SendInvitePostRequest inviteRequest = new SendInvitePostRequest( + documentId, + to, + "sender@signnow.com", // You can use your own sender email + "Document Signing Request - Action Required", + "Dear " + recipientName + ", please review and sign the uploaded document." + ); + inviteRequest.withDocumentId(documentId); + + client.send(inviteRequest); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Recipient added and invite sent successfully"); + return ResponseEntity.ok(response); + } + + private ResponseEntity uploadAndCreateDocumentGroup(Map data, ApiClient client) throws SignNowApiException, IOException { + // This method is for JSON requests without file upload + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("message", "File upload required for this action"); + return ResponseEntity.badRequest().body(errorResponse); + } + + private ResponseEntity uploadAndCreateDocumentGroupWithFile(MultipartHttpServletRequest request, ApiClient client) throws SignNowApiException, IOException { + MultipartFile file = request.getFile("document_file"); + + if (file == null || file.isEmpty()) { + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("message", "No file uploaded"); + return ResponseEntity.badRequest().body(errorResponse); + } + + String documentName = file.getOriginalFilename(); + + // Validate file type + if (documentName == null || !documentName.toLowerCase().endsWith(".pdf")) { + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("message", "Only PDF files are allowed"); + return ResponseEntity.badRequest().body(errorResponse); + } + + // Check file size (max 50MB) + if (file.getSize() > 50 * 1024 * 1024) { + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("message", "File size too large. Maximum 50MB allowed"); + return ResponseEntity.badRequest().body(errorResponse); + } + + // Create temporary file + Path tempFile = Files.createTempFile("upload", ".pdf"); + file.transferTo(tempFile.toFile()); + + try { + // Upload PDF file to SignNow + Map documentResponse = uploadDocument(client, tempFile.toFile(), documentName); + if (!(Boolean) documentResponse.get("success")) { + return ResponseEntity.status(500).body(documentResponse); + } + + String documentId = (String) documentResponse.get("document_id"); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Document uploaded successfully"); + response.put("document_id", documentId); + return ResponseEntity.ok(response); + } finally { + // Clean up temporary file + Files.deleteIfExists(tempFile); + } + } + + private ResponseEntity createEmbeddedEditLink(Map data, ApiClient client) throws SignNowApiException { + String documentId = (String) data.get("document_id"); + + if (documentId == null || documentId.isEmpty()) { + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("message", "Document ID is required"); + return ResponseEntity.badRequest().body(errorResponse); + } + + // Create embedded editor link for document + String redirectUrl = "http://localhost:8080/samples/UploadEmbeddedEditingAndInvite?" + + "page=invite-page&document_id=" + documentId; + + DocumentEmbeddedEditorLinkPostRequest editLinkReq = new DocumentEmbeddedEditorLinkPostRequest( + redirectUrl, + "self", + 15 + ).withDocumentId(documentId); + + DocumentEmbeddedEditorLinkPostResponse response = (DocumentEmbeddedEditorLinkPostResponse) client.send(editLinkReq).getResponse(); + + Map result = new HashMap<>(); + result.put("success", true); + result.put("edit_link", response.getData().getUrl()); + return ResponseEntity.ok(result); + } + + private ResponseEntity createInvite(Map data, ApiClient client) throws SignNowApiException { + String documentId = (String) data.get("document_id"); + String signerEmail = (String) data.get("signer_email"); + String signerName = (String) data.get("signer_name"); + + if (documentId == null || documentId.isEmpty() || + signerEmail == null || signerEmail.isEmpty() || + signerName == null || signerName.isEmpty()) { + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("message", "Document ID, signer email and name are required"); + return ResponseEntity.badRequest().body(errorResponse); + } + + // Get document info and recipients + DocumentGetResponse documentGetResponse = getDocument(client, documentId); + List> recipients = getDocumentRecipients(client, documentId); + + // Create invite for the document using real recipients + var roles = documentGetResponse.getRoles(); + ToCollection to = new ToCollection(); + + for (var role : roles) { + // Find recipient for this role + Map recipient = recipients.stream() + .filter(rec -> role.getUniqueId().equals(rec.get("role_id"))) + .findFirst() + .orElse(null); + + // Use recipient email or fallback to provided email + String emailToUse = recipient != null ? (String) recipient.get("email") : signerEmail; + String nameToUse = recipient != null ? (String) recipient.get("role") : signerName; + + to.add(new To( + emailToUse, + role.getUniqueId(), + role.getName(), + Integer.parseInt(role.getSigningOrder()), + "Document Signing Request - Action Required", + "Dear " + nameToUse + ", please review and sign the uploaded document." + )); + } + + // Create and send invite request for document + SendInvitePostRequest inviteRequest = new SendInvitePostRequest( + documentId, + to, + "sender@signnow.com", // You can use your own sender email + "Document Signing Request - Action Required", + "Dear " + signerName + ", please review and sign the uploaded document." + ); + inviteRequest.withDocumentId(documentId); + + client.send(inviteRequest); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Invite sent successfully"); + return ResponseEntity.ok(response); + } + + private ResponseEntity getInviteStatus(Map data, ApiClient client) throws SignNowApiException { + String documentId = (String) data.get("document_id"); + List> signers = getDocumentSignersStatus(client, documentId); + return ResponseEntity.ok(signers); + } + + private ResponseEntity downloadDocument(Map data, ApiClient client) throws SignNowApiException, IOException { + String documentId = (String) data.get("document_id"); + byte[] fileContents = downloadDocumentFile(client, documentId); + + return ResponseEntity.ok() + .header("Content-Type", "application/pdf") + .header("Content-Disposition", "attachment; filename=\"final_document.pdf\"") + .body(fileContents); + } + + private Map uploadDocument(ApiClient client, File file, String documentName) throws SignNowApiException { + // Create document upload request + DocumentPostRequest documentPost = new DocumentPostRequest(file, documentName); + + DocumentPostResponse response = (DocumentPostResponse) client.send(documentPost).getResponse(); + + Map result = new HashMap<>(); + result.put("success", true); + result.put("document_id", response.getId()); + return result; + } + + private String createDocumentGroup(ApiClient client, String documentId) throws SignNowApiException { + // Create document group with the uploaded document + DocumentIdCollection documentIdCollection = new DocumentIdCollection(); + documentIdCollection.add(documentId); + + DocumentGroupPostRequest documentGroupPost = new DocumentGroupPostRequest( + documentIdCollection, + "Uploaded Document Group" + ); + + DocumentGroupPostResponse response = (DocumentGroupPostResponse) client.send(documentGroupPost).getResponse(); + return response.getId(); + } + + private DocumentGroupGetResponse getDocumentGroup(ApiClient client, String documentGroupId) throws SignNowApiException { + DocumentGroupGetRequest request = new DocumentGroupGetRequest(); + request.withDocumentGroupId(documentGroupId); + return (DocumentGroupGetResponse) client.send(request).getResponse(); + } + + private DocumentGetResponse getDocument(ApiClient client, String documentId) throws SignNowApiException { + DocumentGetRequest request = new DocumentGetRequest(); + request.withDocumentId(documentId); + return (DocumentGetResponse) client.send(request).getResponse(); + } + + private List getDocumentRoles(ApiClient client, String documentId) throws SignNowApiException { + DocumentGetResponse document = getDocument(client, documentId); + List roles = new LinkedList<>(); + + for (var role : document.getRoles()) { + String roleName = role.getName(); + if (roleName != null && !roleName.isEmpty() && !roles.contains(roleName)) { + roles.add(roleName); + } + } + + return roles; + } + + private List> getDocumentRecipients(ApiClient client, String documentId) throws SignNowApiException { + // Get document info + DocumentGetResponse documentGetResponse = getDocument(client, documentId); + List> recipients = new LinkedList<>(); + + // Get roles and create basic recipient info + var roles = documentGetResponse.getRoles(); + for (var role : roles) { + Map recipient = new HashMap<>(); + recipient.put("email", ""); // Empty email, will be filled when adding recipients + recipient.put("role", role.getName()); + recipient.put("role_id", role.getUniqueId()); + recipient.put("signing_order", Integer.parseInt(role.getSigningOrder())); + recipient.put("inviter_role", false); // Default value + recipients.add(recipient); + } + + return recipients; + } + + private List> getDocumentSignersStatus(ApiClient client, String documentId) throws SignNowApiException { + DocumentGetRequest request = new DocumentGetRequest(); + request.withDocumentId(documentId); + + DocumentGetResponse response = (DocumentGetResponse) client.send(request).getResponse(); + var invites = response.getFieldInvites(); + + List> statuses = new LinkedList<>(); + for (var invite : invites) { + Map status = new HashMap<>(); + status.put("name", invite.getEmail() != null ? invite.getEmail() : ""); + status.put("timestamp", invite.getUpdated() != null ? + java.time.Instant.ofEpochSecond(Long.parseLong(invite.getUpdated())).toString() : ""); + status.put("status", invite.getStatus()); + statuses.add(status); + } + return statuses; + } + + private List> getDocumentGroupSignersStatus(ApiClient client, String documentGroupId) throws SignNowApiException { + DocumentGroupRecipientsGetResponse recipientsResponse = getDocumentGroupRecipients(client, documentGroupId); + List> signers = new LinkedList<>(); + + // Get invite status for the document group + DocumentGroupGetResponse docGroup = getDocumentGroup(client, documentGroupId); + String inviteId = docGroup.getInviteId(); + + if (inviteId == null || inviteId.isEmpty()) { + // If no invite exists, return basic recipient info + for (var recipient : recipientsResponse.getData().getRecipients()) { + Map signer = new HashMap<>(); + signer.put("name", recipient.getName()); + signer.put("email", recipient.getEmail()); + signer.put("status", "not_invited"); + signer.put("order", recipient.getOrder()); + signer.put("timestamp", null); + signers.add(signer); + } + return signers; + } + + GroupInviteGetRequest inviteStatusRequest = new GroupInviteGetRequest() + .withDocumentGroupId(documentGroupId) + .withInviteId(inviteId); + + GroupInviteGetResponse inviteStatusResponse = (GroupInviteGetResponse) client.send(inviteStatusRequest).getResponse(); + + Map statuses = new HashMap<>(); + for (var step : inviteStatusResponse.getInvite().getSteps()) { + for (var action : step.getActions()) { + statuses.put(action.getRoleName(), action.getStatus()); + } + } + + for (var recipient : recipientsResponse.getData().getRecipients()) { + Map signer = new HashMap<>(); + signer.put("name", recipient.getName()); + signer.put("email", recipient.getEmail()); + signer.put("status", statuses.getOrDefault(recipient.getName(), "unknown")); + signer.put("order", recipient.getOrder()); + signer.put("timestamp", null); // Document Group doesn't provide individual timestamps + signers.add(signer); + } + + return signers; + } + + private DocumentGroupRecipientsGetResponse getDocumentGroupRecipients(ApiClient client, String documentGroupId) throws SignNowApiException { + DocumentGroupRecipientsGetRequest recipientsRequest = new DocumentGroupRecipientsGetRequest() + .withDocumentGroupId(documentGroupId); + + return (DocumentGroupRecipientsGetResponse) client.send(recipientsRequest).getResponse(); + } + + private byte[] downloadDocumentGroupFile(ApiClient client, String documentGroupId) throws SignNowApiException, IOException { + var orderColl = new com.signnow.api.documentgroup.request.data.DocumentOrderCollection(); + DownloadDocumentGroupPostRequest downloadRequest = new DownloadDocumentGroupPostRequest( + "merged", + "no", + orderColl + ).withDocumentGroupId(documentGroupId); + + DownloadDocumentGroupPostResponse response = (DownloadDocumentGroupPostResponse) client.send(downloadRequest).getResponse(); + + byte[] content = Files.readAllBytes(response.getFile().toPath()); + response.getFile().delete(); + + return content; + } + + private byte[] downloadDocumentFile(ApiClient client, String documentId) throws SignNowApiException, IOException { + // For single document download, we'll use DocumentDownloadGet + DocumentDownloadGetRequest downloadRequest = new DocumentDownloadGetRequest(); + downloadRequest.withDocumentId(documentId) + .withType("collapsed") + .withHistory("no"); + + DocumentDownloadGetResponse response = (DocumentDownloadGetResponse) client.send(downloadRequest).getResponse(); + + byte[] content = Files.readAllBytes(response.getFile().toPath()); + response.getFile().delete(); + + return content; + } +} diff --git a/src/main/resources/static/samples/UploadEmbeddedEditingAndInvite/README.md b/src/main/resources/static/samples/UploadEmbeddedEditingAndInvite/README.md new file mode 100644 index 0000000..8c7a559 --- /dev/null +++ b/src/main/resources/static/samples/UploadEmbeddedEditingAndInvite/README.md @@ -0,0 +1,172 @@ +# Upload Embedded Editing and Invite Sample + +This sample demonstrates a complete workflow for uploading a PDF document to SignNow, creating an embedded editing interface, and sending signing invites to recipients. It combines document upload, embedded editing, and invite management capabilities using the SignNow Java SDK. + +## Use Case Overview + +This sample is designed for scenarios where you need to: +- Upload a PDF document to SignNow from user's local device +- Allow users to edit the document using SignNow's embedded editor +- Send signing invites to designated recipients +- Track signing status and download completed documents + +**Target Audience**: Java developers implementing document workflows that require upload, editing, and signing capabilities in a single integrated flow. + +## Scenario Description + +This sample demonstrates a business workflow where a user uploads a document from their local device, edits it using SignNow's embedded editor, and then sends it for signing. The workflow includes: + +1. **Document Upload**: User selects and uploads a PDF file from their device to SignNow +2. **Embedded Editing**: Provide an embedded editor interface for document modification +3. **Invite Management**: Send signing invites to designated recipients +4. **Status Tracking**: Monitor signing progress and download completed documents + +### Step-by-step: + +1. **Upload Page**: User selects a PDF file from their device and uploads it to SignNow +2. **Embedded Editor**: SignNow interface for document editing (external URL) +3. **Invite Page**: User provides signer information and sends invite +4. **Status Page**: Monitor signing progress and download completed document + +### Page Flow Documentation: + +- **HTML Container Pages**: + - `
` - Initial document upload interface + - `
` - Form for entering signer information and managing recipients + - `
` - Status tracking and download interface + +- **Embedded SignNow Pages**: + - Embedded Editor URL - External SignNow interface for document editing + - Redirects back to invite page after editing completion + +## Technical Flow + +| Action | Responsibility | Code Location | +|--------|---------------|---------------| +| Document Upload | Backend | `uploadAndCreateDocumentGroupWithFile()` | +| Generate Edit Link | Backend | `createEmbeddedEditLink()` | +| Send Signing Invite | Backend | `createInvite()` | +| Track Status | Backend | `getInviteStatus()` | +| Download Document | Backend | `downloadDocument()` | +| Get Recipients | Backend | `getDocumentRecipientsApi()` | +| Add Recipient | Backend | `addDocumentRecipient()` | +| Get Document Roles | Backend | `getDocumentRolesApi()` | + +### Page Navigation Flow: + +- **HTML Container → Embedded SignNow**: Redirect to external SignNow editor URL +- **Embedded SignNow → HTML Container**: Return via redirect URL with query parameters +- **HTML Container → HTML Container**: Direct navigation using `?page=` parameter + +## Sequence of Function Calls + +1. **Frontend**: `initUploadPage()` - Initiates document upload +2. **Backend**: `uploadAndCreateDocumentGroupWithFile()` - Uploads PDF and creates document +3. **Frontend**: `initInvitePage()` - Provides access to embedded editor +4. **Backend**: `createEmbeddedEditLink()` - Generates embedded editor URL +5. **Frontend**: `initInvitePage()` - Collects signer information +6. **Backend**: `createInvite()` - Sends signing invite to recipient +7. **Frontend**: `initStatusPage()` - Monitors signing status +8. **Backend**: `getInviteStatus()` - Retrieves current signing status +9. **Backend**: `downloadDocument()` - Downloads completed document + +### Page Navigation Sequence: + +- Frontend page routing (`handlePages()` function) +- Backend embedded URL generation with redirects +- SignNow API calls for embedded interfaces +- Return flow from embedded pages to HTML containers + +## Template Info + +This sample allows users to upload their own PDF files from their local device. The uploaded document should contain appropriate roles (such as "Signer") for the invite functionality to work correctly. + +**Document Requirements**: +- PDF format only +- Contains signing roles for invite functionality +- Compatible with SignNow's document processing +- File size within SignNow's limits (50MB max) + +## Configuration + +### Required Environment Variables + +- `SIGNNOW_CLIENT_ID` - SignNow API client ID +- `SIGNNOW_CLIENT_SECRET` - SignNow API client secret +- `SIGNNOW_USERNAME` - SignNow account username +- `SIGNNOW_PASSWORD` - SignNow account password + +### Setup Instructions + +1. Configure SignNow API credentials in your environment +2. Run the Spring Boot application: `mvn spring-boot:run` +3. Navigate to the sample: `http://localhost:8080/samples/UploadEmbeddedEditingAndInvite` +4. Prepare a PDF file with signing roles for testing +5. Follow the workflow steps as described + +## Quick Start (TL;DR) + +1. **Start**: Navigate to `/samples/UploadEmbeddedEditingAndInvite` +2. **Upload**: Select a PDF file from your device and upload it +3. **Edit**: Click "Open Document Editor" to access embedded editor +4. **Invite**: Click "Send Signing Invite" to send invite +5. **Monitor**: Check status and download completed document + +**URL Patterns**: +- Initial: `/samples/UploadEmbeddedEditingAndInvite` +- Invite: `/samples/UploadEmbeddedEditingAndInvite?page=invite-page&document_id={id}` +- Status: `/samples/UploadEmbeddedEditingAndInvite?page=status-page&document_id={id}` + +**Expected Flow**: Select File → Upload → Edit → Invite → Sign → Download + +## Java-Specific Implementation Details + +### Controller Structure + +The Java implementation uses Spring Boot with the following key components: + +- **IndexController**: Main controller implementing `ExampleInterface` +- **MultipartFile Support**: Handles file uploads via `MultipartHttpServletRequest` +- **JSON API**: Processes JSON requests for non-file operations +- **Error Handling**: Comprehensive error handling with proper HTTP status codes + +### Key Methods + +- `handleGet()`: Returns HTML page for the sample +- `handlePost(String)`: Handles JSON-based API calls +- `handlePost(MultipartHttpServletRequest)`: Handles file uploads +- Various private methods for SignNow API interactions + +### File Upload Handling + +The Java version includes robust file upload validation: +- File type validation (PDF only) +- File size limits (50MB maximum) +- Temporary file management with proper cleanup +- Error handling for invalid uploads + +### API Endpoints + +All endpoints follow the pattern `/api/samples/UploadEmbeddedEditingAndInvite` with POST method: +- `upload_and_create_dg` - File upload +- `create_embedded_edit` - Generate edit link +- `create_invite` - Send signing invite +- `invite-status` - Get signing status +- `download-document` - Download completed document +- `get-recipients` - Get document recipients +- `add-recipient` - Add new recipient +- `get-document-roles` - Get available roles + +## Disclaimer + +This sample is designed for demonstration and educational purposes. For production use, consider: + +- **Security**: Implement proper authentication and authorization +- **Error Handling**: Add comprehensive error handling and user feedback +- **Validation**: Validate all user inputs and file uploads +- **Customization**: Adapt the workflow to your specific business requirements +- **Performance**: Optimize for your expected load and usage patterns +- **File Storage**: Consider secure file storage and cleanup policies +- **Logging**: Implement proper logging for debugging and monitoring + +The sample demonstrates core SignNow Java SDK functionality but may require additional security measures and customization for production deployment. diff --git a/src/main/resources/static/samples/UploadEmbeddedEditingAndInvite/index.html b/src/main/resources/static/samples/UploadEmbeddedEditingAndInvite/index.html new file mode 100644 index 0000000..45c6fbd --- /dev/null +++ b/src/main/resources/static/samples/UploadEmbeddedEditingAndInvite/index.html @@ -0,0 +1,497 @@ + + + + + + Upload Embedded Editing and Invite - SignNow Sample + + + + + +
+ Logo +
+ + +
+

Upload Document for Editing and Signing

+

This sample demonstrates uploading a PDF file to SignNow, creating an embedded editing link, and sending invites for signing.

+ +
+
+ + + Please select a PDF file to upload +
+ + + +
+ + +
+ + + + + + + + + + +