Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client-lib/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>com.signnow</groupId>
<artifactId>api-client-lib</artifactId>
<version>1.0.7</version>
<version>1.0.8</version>
<packaging>jar</packaging>

<properties>
Expand Down
145 changes: 145 additions & 0 deletions client-lib/src/main/java/com/signnow/library/dto/DocumentGroup.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.signnow.library.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@JsonIgnoreProperties(ignoreUnknown = true)
public class DocumentGroup extends GenericId {
Expand Down Expand Up @@ -57,4 +61,145 @@ public static class DocumentGroupsListResponce {
@JsonProperty("document_group_total_count")
public Integer totalCount;
}

/*------------------------Embedded group invites------------------------*/

@JsonInclude(JsonInclude.Include.NON_NULL)
public static class EmbeddedGroupInviteRequest {
public final List<EmbeddedGroupInviteStep> invites;
/**
* When true the signer gets one merged signing session for the whole group
*/
@JsonProperty("sign_as_merged")
public Boolean signAsMerged = Boolean.TRUE;

public EmbeddedGroupInviteRequest(EmbeddedGroupInviteStep... steps) {
this.invites = new ArrayList<>(Arrays.asList(steps));
}
}

@JsonInclude(JsonInclude.Include.NON_NULL)
public static class EmbeddedGroupInviteStep {
public final int order;
public final List<EmbeddedGroupInviteSigner> signers;

public EmbeddedGroupInviteStep(int order, EmbeddedGroupInviteSigner... signers) {
this.order = order;
this.signers = new ArrayList<>(Arrays.asList(signers));
}
}

@JsonInclude(JsonInclude.Include.NON_NULL)
public static class EmbeddedGroupInviteSigner {
public String email;
@JsonProperty("auth_method")
public String authMethod = "none";
@JsonProperty("first_name")
public String firstName;
@JsonProperty("last_name")
public String lastName;
public String language;
public final List<EmbeddedGroupInviteDocument> documents;

public EmbeddedGroupInviteSigner(String email, EmbeddedGroupInviteDocument... documents) {
this.email = email;
this.documents = new ArrayList<>(Arrays.asList(documents));
}
}

@JsonInclude(JsonInclude.Include.NON_NULL)
public static class EmbeddedGroupInviteDocument {
public final String id;
public String action = "sign";
public final String role;

/**
* @param documentId id of a document belonging to the group
* @param role role NAME (e.g. "Signer 1", as listed in {@link DocumentInfo#roles}),
* NOT a role id — unlike the per-document embedded invite API
*/
public EmbeddedGroupInviteDocument(String documentId, String role) {
this.id = documentId;
this.role = role;
}
}

@JsonIgnoreProperties(ignoreUnknown = true)
public static class EmbeddedGroupInviteResponse {
public InviteData data;

public String getId() {
return data != null ? data.id : null;
}

@JsonIgnoreProperties(ignoreUnknown = true)
public static class InviteData {
public String id;
}
}

@JsonInclude(JsonInclude.Include.NON_NULL)
public static class EmbeddedGroupInviteLinkRequest {
/**
* Identifies the signer the link is minted for (unlike the per-document link
* mint, which addresses the signer in the URL)
*/
public String email;
@JsonProperty("auth_method")
public String authMethod = "none";
@JsonProperty("link_expiration")
public Integer linkExpiration = 45;

public EmbeddedGroupInviteLinkRequest(String email) {
this.email = email;
}
}

@JsonIgnoreProperties(ignoreUnknown = true)
public static class EmbeddedGroupInviteLinkResponse {
public Map<String, String> data = new HashMap<>();

public String getLink() {
return data.get("link");
}
}

/*------------------------Document group info v2------------------------*/

@JsonIgnoreProperties(ignoreUnknown = true)
public static class DocumentGroupV2Response {
public DocumentGroupV2Info data;
}

@JsonIgnoreProperties(ignoreUnknown = true)
public static class DocumentGroupV2Info {
public String id;
public String name;
@JsonProperty("invite_id")
public String inviteId;
@JsonProperty("pending_step_id")
public String pendingStepId;
public String state;
@JsonProperty("last_invite_id")
public String lastInviteId;
public List<DocumentGroupV2Document> documents = new ArrayList<>();
}

@JsonIgnoreProperties(ignoreUnknown = true)
public static class DocumentGroupV2Document {
public String id;
public List<String> roles = new ArrayList<>();
@JsonProperty("document_name")
public String documentName;
@JsonProperty("field_invites")
public List<DocumentGroupV2FieldInvite> fieldInvites = new ArrayList<>();
}

@JsonIgnoreProperties(ignoreUnknown = true)
public static class DocumentGroupV2FieldInvite {
public String id;
public String status;
@JsonProperty("signer_email")
public String signerEmail;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,12 @@ public interface DocumentGroups {
String createDocumentGroupInvite(String documentGroupId, GroupInvite groupInvite) throws SNException;

void resendInvites(String documentGroupId, String inviteId, String email) throws SNException;

DocumentGroup.EmbeddedGroupInviteResponse createDocumentGroupEmbeddedInvite(String documentGroupId, DocumentGroup.EmbeddedGroupInviteRequest request) throws SNException;

String getDocumentGroupEmbeddedInviteLink(String documentGroupId, String embeddedInviteId, DocumentGroup.EmbeddedGroupInviteLinkRequest request) throws SNException;

DocumentGroup.DocumentGroupV2Response getDocumentGroupV2(String documentGroupId) throws SNException;

void deleteDocumentGroupEmbeddedInvite(String documentGroupId) throws SNException;
}
Comment thread
VitaliiSkuratovskyi marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,43 @@ public void resendInvites(String documentGroupId, String inviteId, String email)
GenericId.class
);
}

@Override
public DocumentGroup.EmbeddedGroupInviteResponse createDocumentGroupEmbeddedInvite(String documentGroupId, DocumentGroup.EmbeddedGroupInviteRequest request) throws SNException {
return client.post(
"/v2/document-groups/{document_group_id}/embedded-invites",
Collections.singletonMap("document_group_id", documentGroupId),
request,
DocumentGroup.EmbeddedGroupInviteResponse.class
);
}

@Override
public String getDocumentGroupEmbeddedInviteLink(String documentGroupId, String embeddedInviteId, DocumentGroup.EmbeddedGroupInviteLinkRequest request) throws SNException {
Map<String, String> params = new HashMap<>();
params.put("document_group_id", documentGroupId);
params.put("embedded_invite_id", embeddedInviteId);
DocumentGroup.EmbeddedGroupInviteLinkResponse response = client.post(
"/v2/document-groups/{document_group_id}/embedded-invites/{embedded_invite_id}/link",
params, request, DocumentGroup.EmbeddedGroupInviteLinkResponse.class);
return response.getLink();
}

@Override
public DocumentGroup.DocumentGroupV2Response getDocumentGroupV2(String documentGroupId) throws SNException {
return client.get(
"/v2/document-groups/{document_group_id}",
Collections.singletonMap("document_group_id", documentGroupId),
DocumentGroup.DocumentGroupV2Response.class
);
}

@Override
public void deleteDocumentGroupEmbeddedInvite(String documentGroupId) throws SNException {
client.delete(
"/v2/document-groups/{document_group_id}/embedded-invites",
Collections.singletonMap("document_group_id", documentGroupId),
String.class
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.Map;

public class WebhookService extends ApiService implements Webhooks {

public WebhookService(SNClient client) {
super(client);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.signnow.library.dto;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
* Wire-format tests for the embedded group invite DTOs: request bodies must carry
* the exact snake_case keys with nulls omitted, responses must tolerate unknown keys
* (the mapper below fails on unknown properties, like the client's Jackson provider).
*/
class DocumentGroupEmbeddedInviteSerializationTest {
private final ObjectMapper mapper = new ObjectMapper();

@Test
void createRequestSerializesExactKeys() throws Exception {
DocumentGroup.EmbeddedGroupInviteDocument document =
new DocumentGroup.EmbeddedGroupInviteDocument("doc123", "Signer 1");
DocumentGroup.EmbeddedGroupInviteSigner signer =
new DocumentGroup.EmbeddedGroupInviteSigner("signer@example.com", document);
signer.firstName = "John";
signer.lastName = "Doe";
signer.language = "en";
DocumentGroup.EmbeddedGroupInviteRequest request = new DocumentGroup.EmbeddedGroupInviteRequest(
new DocumentGroup.EmbeddedGroupInviteStep(1, signer));

JsonNode actual = mapper.readTree(mapper.writeValueAsString(request));
JsonNode expected = mapper.readTree("{"
+ "\"invites\":[{\"order\":1,\"signers\":[{"
+ "\"email\":\"signer@example.com\",\"auth_method\":\"none\","
+ "\"first_name\":\"John\",\"last_name\":\"Doe\",\"language\":\"en\","
+ "\"documents\":[{\"id\":\"doc123\",\"action\":\"sign\",\"role\":\"Signer 1\"}]"
+ "}]}],"
+ "\"sign_as_merged\":true}");

assertEquals(expected, actual);
}

@Test
void createRequestOmitsNullOptionalFields() throws Exception {
DocumentGroup.EmbeddedGroupInviteRequest request = new DocumentGroup.EmbeddedGroupInviteRequest(
new DocumentGroup.EmbeddedGroupInviteStep(1,
new DocumentGroup.EmbeddedGroupInviteSigner("signer@example.com",
new DocumentGroup.EmbeddedGroupInviteDocument("doc123", "Signer 1"))));

JsonNode signer = mapper.readTree(mapper.writeValueAsString(request))
.get("invites").get(0).get("signers").get(0);

assertFalse(signer.has("first_name"));
assertFalse(signer.has("last_name"));
assertFalse(signer.has("language"));
}

@Test
void linkRequestSerializesExactKeys() throws Exception {
DocumentGroup.EmbeddedGroupInviteLinkRequest request =
new DocumentGroup.EmbeddedGroupInviteLinkRequest("signer@example.com");

JsonNode actual = mapper.readTree(mapper.writeValueAsString(request));
JsonNode expected = mapper.readTree(
"{\"email\":\"signer@example.com\",\"auth_method\":\"none\",\"link_expiration\":45}");

assertEquals(expected, actual);
}

@Test
void createResponseToleratesUnknownKeys() throws Exception {
DocumentGroup.EmbeddedGroupInviteResponse response = mapper.readValue(
"{\"data\":{\"id\":\"invite123\",\"unexpected\":\"x\"},\"extra\":1}",
DocumentGroup.EmbeddedGroupInviteResponse.class);

assertEquals("invite123", response.getId());
}

@Test
void linkResponseToleratesUnknownKeys() throws Exception {
DocumentGroup.EmbeddedGroupInviteLinkResponse response = mapper.readValue(
"{\"data\":{\"link\":\"https://example.com/sign\"},\"extra\":\"x\"}",
DocumentGroup.EmbeddedGroupInviteLinkResponse.class);

assertEquals("https://example.com/sign", response.getLink());
}

@Test
void groupInfoV2ResponseToleratesUnknownKeys() throws Exception {
DocumentGroup.DocumentGroupV2Response response = mapper.readValue("{\"data\":{"
+ "\"id\":\"group123\",\"name\":\"Bundle\",\"created\":1712233114,"
+ "\"invite_id\":null,\"pending_step_id\":\"step1\",\"state\":\"pending\","
+ "\"last_invite_id\":\"inv1\",\"folder_id\":\"f1\","
+ "\"documents\":[{\"id\":\"doc1\",\"roles\":[\"Signer 1\"],"
+ "\"document_name\":\"Contract\",\"updated\":1712233114,"
+ "\"field_invites\":[{\"id\":\"fi1\",\"status\":\"pending\","
+ "\"signer_email\":\"signer@example.com\",\"created\":1712233114}]}],"
+ "\"freeform_invite\":{\"id\":null,\"last_id\":null}}}",
DocumentGroup.DocumentGroupV2Response.class);

assertEquals("group123", response.data.id);
assertNull(response.data.inviteId);
assertEquals("pending", response.data.state);
assertEquals("doc1", response.data.documents.get(0).id);
assertTrue(response.data.documents.get(0).roles.contains("Signer 1"));
assertEquals("pending", response.data.documents.get(0).fieldInvites.get(0).status);
assertEquals("signer@example.com", response.data.documents.get(0).fieldInvites.get(0).signerEmail);
}
}
Loading