All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hellosign.sdk.HelloSignClient Maven / Gradle / Ivy

package com.hellosign.sdk;

/**
 * The MIT License (MIT)
 * 
 * Copyright (C) 2015 hellosign.com
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

import java.io.File;
import java.lang.reflect.Constructor;
import java.net.HttpURLConnection;
import java.util.List;
import java.util.Map;

import org.json.JSONException;
import org.json.JSONObject;

import com.hellosign.sdk.http.Authentication;
import com.hellosign.sdk.http.HttpClient;
import com.hellosign.sdk.resource.AbstractRequest;
import com.hellosign.sdk.resource.AbstractResourceList;
import com.hellosign.sdk.resource.Account;
import com.hellosign.sdk.resource.ApiApp;
import com.hellosign.sdk.resource.EmbeddedRequest;
import com.hellosign.sdk.resource.EmbeddedResponse;
import com.hellosign.sdk.resource.FileUrlResponse;
import com.hellosign.sdk.resource.SignatureRequest;
import com.hellosign.sdk.resource.Team;
import com.hellosign.sdk.resource.Template;
import com.hellosign.sdk.resource.TemplateDraft;
import com.hellosign.sdk.resource.TemplateSignatureRequest;
import com.hellosign.sdk.resource.UnclaimedDraft;
import com.hellosign.sdk.resource.support.ApiAppList;
import com.hellosign.sdk.resource.support.OauthData;
import com.hellosign.sdk.resource.support.Signature;
import com.hellosign.sdk.resource.support.SignatureRequestList;
import com.hellosign.sdk.resource.support.TemplateList;
import com.hellosign.sdk.resource.support.types.FieldType;

/**
 * You'll need the HelloSignClient to do just about everything, from creating
 * signatures to updating account information.
 * 
 * To use this class, instantiate a client with valid credentials like so:
 * 
 * HelloSignClient client = new HelloSignClient(user, key);
 * 
 * Then, use the client to perform your requests. The client uses java.net.HttpURLConnection
 * to perform its HTTP requests.
 * 
 * @author "Chris Paul ([email protected])"
 */
public class HelloSignClient {

    public static final String DEFAULT_ENCODING = "UTF-8";
    public static final String DEFAULT_BASE_API_URL = "https://api.hellosign.com/v3";
    public static final String DEFAULT_OAUTH_TOKEN_URL = "https://app.hellosign.com/oauth/token";

    public static final String ACCOUNT_URI = "/account";
    public static final String VALIDATE_ACCOUNT_URI = "/account/validate";
    public static final String ACCOUNT_CREATE_URI = "/account/create";
    public static final String TEAM_URI = "/team";
    public static final String TEAM_CREATE_URI = "/team/create";
    public static final String TEAM_DESTROY_URI = "/team/destroy";
    public static final String TEAM_ADD_MEMBER_URI = "/team/add_member";
    public static final String TEAM_REMOVE_MEMBER_URI = "/team/remove_member";
    public static final String SIGNATURE_REQUEST_URI = "/signature_request";
    public static final String SIGNATURE_REQUEST_LIST_URI = "/signature_request/list";
    public static final String SIGNATURE_REQUEST_SEND_URI = "/signature_request/send";
    public static final String TEMPLATE_URI = "/template";
    public static final String TEMPLATE_FILE_URI = "/template/files";
    public static final String TEMPLATE_LIST_URI = "/template/list";
    public static final String TEMPLATE_ADD_USER_URI = "/template/add_user";
    public static final String TEMPLATE_REMOVE_USER_URI = "/template/remove_user";
    public static final String TEMPLATE_DELETE_URI = "/template/delete";
    public static final String TEMPLATE_UPDATE_FILES_URI = "/template/update_files";
    public static final String TEMPLATE_CREATE_EMBEDDED_DRAFT_URI = "/template/create_embedded_draft";
    public static final String TEMPLATE_SIGNATURE_REQUEST_URI = "/signature_request/send_with_template";
    public static final String SIGNATURE_REQUEST_CANCEL_URI = "/signature_request/cancel";
    public static final String SIGNATURE_REQUEST_REMIND_URI = "/signature_request/remind";
    public static final String SIGNATURE_REQUEST_FINAL_COPY_URI = "/signature_request/final_copy";
    public static final String SIGNATURE_REQUEST_FILES_URI = "/signature_request/files";
    public static final String SIGNATURE_REQUEST_UPDATE_URI = "/signature_request/update";
    public static final String SIGNATURE_REQUEST_EMBEDDED_URI = "/signature_request/create_embedded";
    public static final String SIGNATURE_REQUEST_EMBEDDED_TEMPLATE_URI = "/signature_request/create_embedded_with_template";
    public static final String EMBEDDED_SIGN_URL_URI = "/embedded/sign_url";
    public static final String EMBEDDED_EDIT_URL_URI = "/embedded/edit_url";
    public static final String UNCLAIMED_DRAFT_CREATE_URI = "/unclaimed_draft/create";
    public static final String UNCLAIMED_DRAFT_CREATE_EMBEDDED_URI = "/unclaimed_draft/create_embedded";
    public static final String UNCLAIMED_DRAFT_CREATE_EMBEDDED_WITH_TEMPLATE_URI = "/unclaimed_draft/create_embedded_with_template";
    public static final String API_APP_URI = "/api_app";
    public static final String API_APP_LIST_URI = "/api_app/list";

    public static final String PARAM_FILE_TYPE_URI = "file_type";
    public static final String PARAM_GET_URL = "get_url";
    public static final String FINAL_COPY_FILE_NAME = "final-copy";
    public static final String FINAL_COPY_FILE_EXT = "pdf";
    public static final String FILES_FILE_NAME = "files";
    public static final String FILES_FILE_EXT = "pdf";
    public static final String TEMPLATE_FILE_NAME = "template";
    public static final String TEMPLATE_FILE_EXT = "pdf";
    public static final String OAUTH_CODE = "code";
    public static final String OAUTH_STATE = "state";
    public static final String OAUTH_GRANT_TYPE = "grant_type";
    public static final String OAUTH_REFRESH_TOKEN = "refresh_token";
    public static final String OAUTH_GRANT_TYPE_AUTHORIZE_CODE = "authorization_code";
    public static final String OAUTH_GRANT_TYPE_REFRESH_TOKEN = "refresh_token";
    public static final String CLIENT_SECRET = "client_secret";
    public static final String CLIENT_ID = "client_id";
    public static final String EMBEDDED_TEMPLATE_SKIP_SIGNER_ROLES = "skip_signer_roles";
    public static final String EMBEDDED_TEMPLATE_SKIP_SUBJECT_MESSAGE = "skip_subject_message";
    public static final String EMBEDDED_TEMPLATE_MERGE_FIELDS = "merge_fields";
    public static final String EMBEDDED_TEMPLATE_CC_ROLES = "cc_roles";

    private Authentication auth;
    private HttpClient httpClient;

    // The base URL for all standard API endpoints, which can be
    // overridden by setting the "hellosign.base.url" system property.
    private String BASE_URI;

    // The base URL for retrieving an OAuth tokens, which can be
    // overridden by setting the "hellosign.oauth.base.url" system property.
    private String OAUTH_TOKEN_URL;

    /**
     * Default constructor for injection of dependencies (testing).
     * @param client HttpClient
     * @param auth Authentication 
     * @see #HelloSignClient(String)
     */
    protected HelloSignClient(HttpClient client, Authentication auth) {
        this.httpClient = client;
        this.auth = auth;

        // Set overrides if present
        BASE_URI = DEFAULT_BASE_API_URL;
        String baseUrl = System.getProperty("hellosign.base.url");
        if (baseUrl != null && !baseUrl.isEmpty()) {
            BASE_URI = baseUrl;
        }
        OAUTH_TOKEN_URL = DEFAULT_OAUTH_TOKEN_URL;
        String customOauthToken = System.getProperty("hellosign.oauth.base.url");
        if (customOauthToken != null && !customOauthToken.isEmpty()) {
            OAUTH_TOKEN_URL = customOauthToken;
        }
    }

    /**
     * Creates a new HelloSign client using your API key.
     * 
     * @param apiKey String API key
     * @throws HelloSignException thrown if there's a problem setting the
     *         credentials
     * @see Account
     *      Settings
     */
    public HelloSignClient(String apiKey) throws HelloSignException {
        this(new HttpClient(), new Authentication(apiKey));
        auth.setApiKey(apiKey);
    }

    /**
     * Creates a new HelloSign client using then given Authentication object.
     * 
     * @param auth Authentication used primarily for setting OAuth token/secret
     * @throws HelloSignException thrown if there's a problem setting the credentials
     */
    public HelloSignClient(Authentication auth) throws HelloSignException {
        this(new HttpClient(), auth);
    }

    /**
     * Allows overriding of the authentication mechanism. Used
     * mainly for setting an OAuth token/secret.
     * @param auth Authentication used for setting the auth method for this client
     */
    public void setAuthentication(Authentication auth) {
        this.auth = auth;
    }

    /**
     * Retrieves the authentication details being used by this client. Used for
     * testing purposes.
     * 
     * @return Authentication
     */
    protected Authentication getAuth() {
        return auth;
    }

    /**
     * Returns the current user's account information.
     * 
     * @return Account
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public Account getAccount() throws HelloSignException {
        return new Account(httpClient.withAuth(auth).get(BASE_URI + ACCOUNT_URI).asJson());
    }

    /**
     * Returns true if an account exists with the provided email address. Note
     * this is limited to the visibility of the currently authenticated user.
     * 
     * @param email String email address
     * @return true if the account exists, false otherwise
     * @throws HelloSignException Thrown if there's a problem communicating with
     *         the HelloSign API.
     */
    public boolean isAccountValid(String email) throws HelloSignException {
        if (email == null || email.isEmpty()) {
            return false;
        }
        Account account = new Account(httpClient.withAuth(auth).withPostField(Account.ACCOUNT_EMAIL_ADDRESS, email)
                .post(BASE_URI + VALIDATE_ACCOUNT_URI).asJson());
        return (account.hasEmail() && email.equalsIgnoreCase(account.getEmail()));
    }

    /**
     * Update your account callback URL.
     * 
     * This URL is used to notify you of any signature request events that occur
     * when your account is a party -- e.g., sender or signer/recipient.
     * 
     * @param callback String URL
     * @return Account
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public Account setCallback(String callback) throws HelloSignException {
        return new Account(httpClient.withAuth(auth).withPostField(Account.ACCOUNT_CALLBACK_URL, callback)
                .post(BASE_URI + ACCOUNT_URI).asJson());
    }

    /**
     * Creates a new HelloSign account. The user will still need to validate
     * their email address to complete the creation process to set a password.
     * Note: This request does not require authentication, so just performs the
     * basic POST.
     * 
     * @param email String New user's email address
     * @return Account New user's account information
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public Account createAccount(String email) throws HelloSignException {
        return createAccount(email, null, null);
    }

    /**
     * Creates a new HelloSign account and provides OAuth app credentials to
     * automatically generate an OAuth token with the user Account response.
     * 
     * @param email String New user's email address
     * @param clientId String Client ID
     * @param clientSecret String App secret
     * @return Account New user's account information
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public Account createAccount(String email, String clientId, String clientSecret) throws HelloSignException {
        HttpClient client = httpClient.withAuth(auth).withPostField(Account.ACCOUNT_EMAIL_ADDRESS, email);
        if (clientId != null && clientSecret != null) {
            client = client.withPostField(CLIENT_ID, clientId).withPostField(CLIENT_SECRET, clientSecret);
        }
        JSONObject response = client.post(BASE_URI + ACCOUNT_CREATE_URI).asJson();
        JSONObject copy;
        try {
            copy = new JSONObject(response.toString());
        } catch (JSONException e) {
            throw new HelloSignException(e);
        }
        OauthData data = new OauthData(copy);
        Account account = new Account(response);
        account.setOauthData(data);
        return account;
    }

    /**
     * Performs an OAuth request and returns the necessary data for authorizing
     * an API application, and will automatically set the access token and code
     * for making authenticated requests with this client.
     * 
     * @param code String OAuth code
     * @param clientId String OAuth client ID
     * @param secret String OAuth secret
     * @param state String OAuth client state
     * @param autoSetRequestToken true if the token should be applied to this
     *        client for future requests
     * @return OauthData object containing OAuth token details
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public OauthData getOauthData(String code, String clientId, String secret, String state,
            boolean autoSetRequestToken) throws HelloSignException {
        OauthData data = new OauthData(httpClient.withAuth(auth).withPostField(OAUTH_STATE, state)
                .withPostField(OAUTH_CODE, code).withPostField(CLIENT_ID, clientId)
                .withPostField(OAUTH_GRANT_TYPE, OAUTH_GRANT_TYPE_AUTHORIZE_CODE).withPostField(CLIENT_SECRET, secret)
                .post(OAUTH_TOKEN_URL).asJson());
        if (data != null && autoSetRequestToken) {
            setAccessToken(data.getAccessToken(), data.getTokenType());
        }
        return data;
    }

    /**
     * Refreshes the OauthData for this client with the provided refresh token.
     * 
     * @param refreshToken String
     * @return OauthData new OAuthData returned from HelloSign
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public OauthData refreshOauthData(String refreshToken) throws HelloSignException {
        OauthData data = new OauthData(
                httpClient.withAuth(auth).withPostField(OAUTH_GRANT_TYPE, OAUTH_GRANT_TYPE_REFRESH_TOKEN)
                        .withPostField(OAUTH_REFRESH_TOKEN, refreshToken).post(OAUTH_TOKEN_URL).asJson());
        if (data != null) {
            setAccessToken(data.getAccessToken(), data.getTokenType());
        }
        return data;
    }

    /**
     * Retrieves the Team for the current user account.
     * 
     * @return Team
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public Team getTeam() throws HelloSignException {
        return new Team(httpClient.withAuth(auth).get(BASE_URI + TEAM_URI).asJson());
    }

    /**
     * Creates a new team for the current user with the given name.
     * 
     * @param teamName String team name
     * @return Team
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public Team createTeam(String teamName) throws HelloSignException {
        return new Team(
                httpClient.withAuth(auth).withPostField(Team.TEAM_NAME, teamName).post(BASE_URI + TEAM_URI).asJson());
    }

    /**
     * Destroys the current user's team.
     * 
     * @return boolean if destroy was successful
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public boolean destroyTeam() throws HelloSignException {
        return HttpURLConnection.HTTP_OK == httpClient.withAuth(auth).post(BASE_URI + TEAM_DESTROY_URI).asHttpCode();
    }

    /**
     * Updates the current user's team name.
     * 
     * @param teamName String team name
     * @return Team
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public Team updateTeamName(String teamName) throws HelloSignException {
        return new Team(
                httpClient.withAuth(auth).withPostField(Team.TEAM_NAME, teamName).post(BASE_URI + TEAM_URI).asJson());
    }

    /**
     * Adds the user to the current user's team.
     * 
     * @param idOrEmail String new team member's account ID or email address
     * @return Team
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public Team inviteTeamMember(String idOrEmail) throws HelloSignException {
        String key = (idOrEmail != null && idOrEmail.contains("@")) ? Account.ACCOUNT_EMAIL_ADDRESS
                : Account.ACCOUNT_ID;
        return new Team(
                httpClient.withAuth(auth).withPostField(key, idOrEmail).post(BASE_URI + TEAM_ADD_MEMBER_URI).asJson());
    }

    /**
     * Removes the team member indicated by the user account ID or email
     * address.
     * 
     * @param idOrEmail String removed team member's account ID or email address
     * @return Team
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public Team removeTeamMember(String idOrEmail) throws HelloSignException {
        String key = (idOrEmail != null && idOrEmail.contains("@")) ? Account.ACCOUNT_EMAIL_ADDRESS
                : Account.ACCOUNT_ID;
        return new Team(httpClient.withAuth(auth).withPostField(key, idOrEmail).post(BASE_URI + TEAM_REMOVE_MEMBER_URI)
                .asJson());
    }

    /**
     * Retrieves a Signature Request with the given ID.
     * 
     * @param id String signature ID
     * @return SignatureRequest
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public SignatureRequest getSignatureRequest(String id) throws HelloSignException {
        String url = BASE_URI + SIGNATURE_REQUEST_URI + "/" + id;
        return new SignatureRequest(httpClient.withAuth(auth).get(url).asJson());
    }

    /**
     * Retrieves the current user's signature requests. The resulting object
     * represents a paged query result. The page information can be retrieved on
     * from the ListInfo object on the SignatureRequestList.
     * 
     * @return SignatureRequestList
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public SignatureRequestList getSignatureRequests() throws HelloSignException {
        return new SignatureRequestList(httpClient.withAuth(auth).get(BASE_URI + SIGNATURE_REQUEST_LIST_URI).asJson());
    }

    /**
     * Retrieves a specific page of the current user's signature requests.
     * 
     * @param page int
     * @return SignatureRequestList
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public SignatureRequestList getSignatureRequests(int page) throws HelloSignException {
        return new SignatureRequestList(
                httpClient.withAuth(auth).withGetParam(AbstractResourceList.PAGE, Integer.toString(page))
                        .get(BASE_URI + SIGNATURE_REQUEST_LIST_URI).asJson());
    }

    /**
     * Retrieves a specific page of the current user's signature requests.
     *
     * @param page int
     * @param pageSize int Must be between 1 and 100.
     * @return SignatureRequestList
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public SignatureRequestList getSignatureRequests(int page, int pageSize) throws HelloSignException {
        return new SignatureRequestList(
                httpClient.withAuth(auth).withGetParam(AbstractResourceList.PAGE, Integer.toString(page))
                        .withGetParam(AbstractResourceList.PAGE_SIZE, Integer.toString(pageSize))
                        .get(BASE_URI + SIGNATURE_REQUEST_LIST_URI).asJson());
    }

    /**
     * Sends the provided signature request to HelloSign.
     * 
     * @param req SignatureRequest
     * @return SignatureRequest
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public SignatureRequest sendSignatureRequest(SignatureRequest req) throws HelloSignException {
        if (req.hasId()) {
            throw new HelloSignException("Sending an existing signature request is not supported");
        }
        return new SignatureRequest(httpClient.withAuth(auth).withPostFields(req.getPostFields())
                .post(BASE_URI + SIGNATURE_REQUEST_SEND_URI).asJson());
    }

    /**
     * Update a signer's email address.
     * 
     * This method requires the ID of the siganture request that has already
     * been sent, as well as the signature_id that represents the signer that
     * should be updated. The ema
     * 
     * @param signatureRequestId String ID of the signature request that has
     *        already been sent and needs to be updated.
     * @param signatureId String ID of the signer that needs to be updated.
     * @param newEmailAddress String email address that the signer should be
     *        changed to
     * @return SignatureRequest The updated request data
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public SignatureRequest updateSignatureRequest(String signatureRequestId, String signatureId,
            String newEmailAddress) throws HelloSignException {
        String url = BASE_URI + SIGNATURE_REQUEST_UPDATE_URI + "/" + signatureRequestId;
        return new SignatureRequest(httpClient.withAuth(auth).withPostField(Signature.SIGNATURE_ID, signatureId)
                .withPostField(SignatureRequest.SIGREQ_SIGNER_EMAIL, newEmailAddress).post(url).asJson());
    }

    /**
     * Retrieves the templates for the current user account.
     * 
     * @return TemplateList
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public TemplateList getTemplates() throws HelloSignException {
        return new TemplateList(httpClient.withAuth(auth).get(BASE_URI + TEMPLATE_LIST_URI).asJson());
    }

    /**
     * Retrieves a page of templates for the current user account.
     * 
     * @param page int
     * @return TemplateList
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public TemplateList getTemplates(int page) throws HelloSignException {
        return new TemplateList(
                httpClient.withAuth(auth).withGetParam(AbstractResourceList.PAGE, Integer.toString(page))
                        .get(BASE_URI + TEMPLATE_LIST_URI).asJson());
    }

    /**
     * Retrieves a page of templates with a specific pageSize
     * @param page int
     * @param pageSize int Must be between 1 and 100.
     * @return TemplateList
     * @throws HelloSignException thrown if there's a problem processing the
     *      HTTP request or the JSON response.
     */
    public TemplateList getTemplates(int page, int pageSize) throws HelloSignException {
        return new TemplateList(
                httpClient.withAuth(auth).withGetParam(AbstractResourceList.PAGE, Integer.toString(page))
                        .withGetParam(AbstractResourceList.PAGE_SIZE, Integer.toString(pageSize))
                        .get(BASE_URI + TEMPLATE_LIST_URI).asJson());
    }

    /**
     * Retrieves the PDF file backing the Template specified by the provided
     * Template ID.
     * 
     * @param templateId String Template ID
     * @return File PDF file object
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public File getTemplateFile(String templateId) throws HelloSignException {
        String url = BASE_URI + TEMPLATE_FILE_URI + "/" + templateId;
        String fileName = TEMPLATE_FILE_NAME + "." + TEMPLATE_FILE_EXT;
        return httpClient.withAuth(auth).get(url).asFile(fileName);
    }

    /**
     * Returns a signed URL that can be used to retrieve the file backing a
     * template.
     * 
     * @param templateId String Template ID
     * @return String URL or null if no file URL can be retrieved
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public String getTemplateFileUrl(String templateId) throws HelloSignException {
        String fileUrl = null;
        String url = BASE_URI + TEMPLATE_FILE_URI + "/" + templateId;
        JSONObject response = httpClient.withAuth(auth).withGetParam(PARAM_GET_URL, "1").get(url).asJson();
        if (response.has("file_url")) {
            try {
                fileUrl = response.getString("file_url");
            } catch (JSONException ex) {
                throw new HelloSignException(ex);
            }
        }
        return fileUrl;
    }

    /**
     * Retrieves a specific Template based on the provided Template ID.
     * 
     * @param templateId String Template ID
     * @return Template
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public Template getTemplate(String templateId) throws HelloSignException {
        String url = BASE_URI + TEMPLATE_URI + "/" + templateId;
        return new Template(httpClient.withAuth(auth).get(url).asJson());
    }

    /**
     * Adds the provided user to the template indicated by the provided template
     * ID. The new user can be designated using their account ID or email
     * address.
     * 
     * @param templateId String template ID
     * @param idOrEmail String account ID or email address
     * @return Template
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public Template addTemplateUser(String templateId, String idOrEmail) throws HelloSignException {
        String url = BASE_URI + TEMPLATE_ADD_USER_URI + "/" + templateId;
        String key = (idOrEmail != null && idOrEmail.contains("@")) ? Account.ACCOUNT_EMAIL_ADDRESS
                : Account.ACCOUNT_ID;
        return new Template(httpClient.withAuth(auth).withPostField(key, idOrEmail).post(url).asJson());
    }

    /**
     * Delete the template designated by the template id
     * 
     * @param templateId String template ID
     * @return true if the delete was successful, false otherwise
     * @throws HelloSignException thrown if there is a problem processing the
     *         HTTP request
     */
    public boolean deleteTemplate(String templateId) throws HelloSignException {
        String url = BASE_URI + TEMPLATE_DELETE_URI + "/" + templateId;
        return HttpURLConnection.HTTP_OK == httpClient.withAuth(auth).post(url).asHttpCode();
    }

    /**
     * Replaces the backing documents for the given template with the given
     * files.
     * 
     * Note that certain rules apply to this endpoint:
     * https://app.hellosign.com/api/reference#update_template_files
     * 
     * @param existingTemplateId String ID of the template from which the
     *        overlay data (signatures, text fields, etc.) will be retrieved.
     * @param newTemplate TemplateDraft that holds the data and documents which
     *        will be used as the basis for the new template. The following
     *        fields can be set in this request: files or file_urls, subject,
     *        message, and test_mode.
     * @param clientId String optional ID of the app which is generating this
     *        new template. Set to null if not used.
     * @return String ID of the template to be created
     * @throws HelloSignException thrown if there is a problem processing the
     *         HTTP request
     */
    public String updateTemplateFiles(String existingTemplateId, TemplateDraft newTemplate, String clientId)
            throws HelloSignException {
        String url = BASE_URI + TEMPLATE_UPDATE_FILES_URI + "/" + existingTemplateId;
        HttpClient client = httpClient.withAuth(auth).withPostFields(newTemplate.getPostFields());
        if (clientId != null) {
            client = client.withPostField(EmbeddedRequest.EMBEDDED_CLIENT_ID, clientId);
        }
        Template t = new Template(client.post(url).asJson());
        return t.getId();
    }

    /**
     * Adds the provided user to the template indicated by the provided template
     * ID. The new user can be designated using their account ID or email
     * address.
     * 
     * @param templateId String template ID
     * @param idOrEmail String account ID or email address
     * @return Template
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public Template removeTemplateUser(String templateId, String idOrEmail) throws HelloSignException {
        String url = BASE_URI + TEMPLATE_REMOVE_USER_URI + "/" + templateId;
        String key = (idOrEmail != null && idOrEmail.contains("@")) ? Account.ACCOUNT_EMAIL_ADDRESS
                : Account.ACCOUNT_ID;
        return new Template(httpClient.withAuth(auth).withPostField(key, idOrEmail).post(url).asJson());
    }

    /**
     * Creates a new Signature Request based on the template provided.
     * 
     * @param req TemplateSignatureRequest
     * @return SignatureRequest
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public SignatureRequest sendTemplateSignatureRequest(TemplateSignatureRequest req) throws HelloSignException {
        return new SignatureRequest(httpClient.withAuth(auth).withPostFields(req.getPostFields())
                .post(BASE_URI + TEMPLATE_SIGNATURE_REQUEST_URI).asJson());
    }

    /**
     * Cancels an existing signature request. If it has been completed, it will
     * delete the signature request from your account.
     * 
     * @param id SignatureRequest id
     * @return boolean true if successful
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public boolean cancelSignatureRequest(String id) throws HelloSignException {
        String url = BASE_URI + SIGNATURE_REQUEST_CANCEL_URI + "/" + id;
        return HttpURLConnection.HTTP_OK == httpClient.withAuth(auth).post(url).asHttpCode();
    }

    /**
     * Instructs HelloSign to email the given address with a reminder to sign
     * the SignatureRequest referenced by the given request ID.
     * 
     * Note: You cannot send a reminder within 1 hours of the last reminder that
     * was sent, manually or automatically.
     * 
     * @param requestId String SignatureRequest ID
     * @param email String email
     * @return SignatureRequest The request to be reminded
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public SignatureRequest requestEmailReminder(String requestId, String email) throws HelloSignException {
        String url = BASE_URI + SIGNATURE_REQUEST_REMIND_URI + "/" + requestId;
        return new SignatureRequest(
                httpClient.withAuth(auth).withPostField(Account.ACCOUNT_EMAIL_ADDRESS, email).post(url).asJson());
    }

    /**
     * Retrieves the final PDF copy of a signature request, if it exists.
     * 
     * @param requestId String SignatureRequest ID
     * @return File final copy file, or null if it does not yet exist
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     * @deprecated Use {{@link #getFiles(String)}
     */
    public File getFinalCopy(String requestId) throws HelloSignException {
        String url = BASE_URI + SIGNATURE_REQUEST_FINAL_COPY_URI + "/" + requestId;
        String filename = FINAL_COPY_FILE_NAME + "." + FINAL_COPY_FILE_EXT;
        return httpClient.withAuth(auth).get(url).asFile(filename);
    }

    /**
     * Retrieves a PDF copy of the files associated with a signature request.
     * 
     * @param requestId String signature ID
     * @return File PDF file
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public File getFiles(String requestId) throws HelloSignException {
        return getFiles(requestId, SignatureRequest.SIGREQ_FORMAT_PDF);
    }

    /**
     * Retrieves the file associated with a signature request.
     * 
     * @param requestId String signature request ID
     * @param format String format, see SignatureRequest for available types
     * @return File
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public File getFiles(String requestId, String format) throws HelloSignException {
        if (format == null || format.isEmpty()) {
            format = FILES_FILE_EXT;
        }
        String url = BASE_URI + SIGNATURE_REQUEST_FILES_URI + "/" + requestId;
        String fileName = FILES_FILE_NAME + "." + format;
        return httpClient.withAuth(auth).withGetParam(PARAM_FILE_TYPE_URI, format).get(url).asFile(fileName);
    }

    /**
     * Retrieves a URL for a file associated with a signature request.
     *
     * @param requestId String signature request ID
     * @return {@link FileUrlResponse}
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     * @see https://app.hellosign.com/api/reference#get_files
     */
    public FileUrlResponse getFilesUrl(String requestId) throws HelloSignException {
        String url = BASE_URI + SIGNATURE_REQUEST_FILES_URI + "/" + requestId;

        HttpClient httpClient = this.httpClient.withAuth(auth).withGetParam(PARAM_GET_URL, "1").get(url);

        if (httpClient.getLastResponseCode() == 404) {
            throw new HelloSignException(String.format("Could not find request with id=%s", requestId));
        }

        return new FileUrlResponse(httpClient.asJson());
    }

    /**
     * Creates a signature request that can be embedded within your website.
     * 
     * @param embeddedReq EmbeddedRequest
     * @return SignatureRequest
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public AbstractRequest createEmbeddedRequest(EmbeddedRequest embeddedReq) throws HelloSignException {
        String url = BASE_URI;
        Class returnType = SignatureRequest.class;
        AbstractRequest req = embeddedReq.getRequest();
        if (req instanceof TemplateSignatureRequest) {
            url += SIGNATURE_REQUEST_EMBEDDED_TEMPLATE_URI;
        } else if (req instanceof UnclaimedDraft) {
            if (((UnclaimedDraft) req).getRequest() instanceof TemplateSignatureRequest) {
                url += UNCLAIMED_DRAFT_CREATE_EMBEDDED_WITH_TEMPLATE_URI;
            } else {
                url += UNCLAIMED_DRAFT_CREATE_EMBEDDED_URI;
            }
            returnType = UnclaimedDraft.class;
        } else {
            url += SIGNATURE_REQUEST_EMBEDDED_URI;
        }
        try {
            Constructor constructor = returnType.getConstructor(JSONObject.class);
            return (AbstractRequest) constructor.newInstance(
                    httpClient.withAuth(auth).withPostFields(embeddedReq.getPostFields()).post(url).asJson());
        } catch (Exception ex) {
            throw new HelloSignException(ex);
        }
    }

    /**
     * Retrieves the necessary information to build an embedded signature
     * request.
     * 
     * @param signatureId String ID of the signature request to embed
     * @return EmbeddedResponse
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public EmbeddedResponse getEmbeddedSignUrl(String signatureId) throws HelloSignException {
        String url = BASE_URI + EMBEDDED_SIGN_URL_URI + "/" + signatureId;
        return new EmbeddedResponse(httpClient.withAuth(auth).post(url).asJson());
    }

    /**
     * Retrieves the necessary information to edit an embedded template.
     * 
     * @param templateId String ID of the signature request to embed
     * @return EmbeddedResponse
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public EmbeddedResponse getEmbeddedTemplateEditUrl(String templateId) throws HelloSignException {
        return getEmbeddedTemplateEditUrl(templateId, false, false, false);
    }

    /**
     * Retrieves the necessary information to edit an embedded template.
     * 
     * @param templateId String ID of the signature request to embed
     * @param skipSignerRoles true if the edited template should not allow the
     *        user to modify the template's signer roles. Defaults to false.
     * @param skipSubjectMessage true if the edited template should not allow
     *        the user to modify the template's subject and message. Defaults to
     *        false.
     * @return EmbeddedResponse
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public EmbeddedResponse getEmbeddedTemplateEditUrl(String templateId, boolean skipSignerRoles,
            boolean skipSubjectMessage) throws HelloSignException {
        return getEmbeddedTemplateEditUrl(templateId, skipSignerRoles, skipSubjectMessage, false);
    }

    /**
     * Retrieves the necessary information to edit an embedded template.
     * 
     * @param templateId String ID of the signature request to embed
     * @param skipSignerRoles true if the edited template should not allow the
     *        user to modify the template's signer roles. Defaults to false.
     * @param skipSubjectMessage true if the edited template should not allow
     *        the user to modify the template's subject and message. Defaults to
     *        false.
     * @param testMode true if this request is a test request. Useful for
     *        editing locked templates.
     * @return EmbeddedResponse
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public EmbeddedResponse getEmbeddedTemplateEditUrl(String templateId, boolean skipSignerRoles,
            boolean skipSubjectMessage, boolean testMode) throws HelloSignException {
        return getEmbeddedTemplateEditUrl(templateId, skipSignerRoles, skipSubjectMessage, false, null, null);
    }
    
    /**
     * Big kahuna method for editing an embedded template. This method allows
     * the updating of merge fields and CC roles (both optional parameters).
     * 
     * @param templateId String ID of the signature request to embed
     * @param skipSignerRoles true if the edited template should not allow the
     *        user to modify the template's signer roles. Defaults to false.
     * @param skipSubjectMessage true if the edited template should not allow
     *        the user to modify the template's subject and message. Defaults to
     *        false.
     * @param testMode true if this request is a test request. Useful for
     *        editing locked templates.
     * @param mergeFields These will overwrite the current merge fields for the
     *        embedded template. To remove all merge fields, pass in an empty Map.
     * @param ccRoles These will overwrite the current CC Roles for the embedded
     *        template. To remove all CC roles, pass in null or an empty List.
     * @return EmbeddedResponse
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public EmbeddedResponse getEmbeddedTemplateEditUrl(String templateId, boolean skipSignerRoles,
            boolean skipSubjectMessage, boolean testMode, Map mergeFields, List ccRoles)
            throws HelloSignException {
        String url = BASE_URI + EMBEDDED_EDIT_URL_URI + "/" + templateId;
        HttpClient client = httpClient.withAuth(auth)
                .withPostField(EMBEDDED_TEMPLATE_SKIP_SIGNER_ROLES, skipSignerRoles)
                .withPostField(EMBEDDED_TEMPLATE_SKIP_SUBJECT_MESSAGE, skipSubjectMessage)
                .withPostField(AbstractRequest.REQUEST_TEST_MODE, testMode);
        String mergeFieldsStr = TemplateDraft.serializeMergeFields(mergeFields);
        if (mergeFieldsStr != null) {
            client = client.withPostField(EMBEDDED_TEMPLATE_MERGE_FIELDS, mergeFieldsStr);
        }
        if (ccRoles == null || ccRoles.isEmpty()) {
            // Per documentation: https://app.hellosign.com/api/reference#get_embedded_template_edit_url
            client = client.withPostField(EMBEDDED_TEMPLATE_CC_ROLES + "[0]", "");
        } else {
            for (int i = 0; i < ccRoles.size(); i++) {
                String cc = ccRoles.get(i);
                client = client.withPostField(EMBEDDED_TEMPLATE_CC_ROLES + "[" + i + "]", cc);
            }
        }
        return new EmbeddedResponse(client.post(url).asJson());
    }

    /**
     * Creates an unclaimed draft using the provided request draft object.
     * 
     * @param draft UnclaimedDraft
     * @return UnclaimedDraft The created draft
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public UnclaimedDraft createUnclaimedDraft(UnclaimedDraft draft) throws HelloSignException {
        String url = BASE_URI;
        if (draft.isForEmbeddedSigning()) {
            url += UNCLAIMED_DRAFT_CREATE_EMBEDDED_URI;
        } else {
            url += UNCLAIMED_DRAFT_CREATE_URI;
        }
        return new UnclaimedDraft(httpClient.withAuth(auth).withPostFields(draft.getPostFields()).post(url).asJson());
    }

    /**
     * Creates a template draft that can be used for embedded template creation.
     * 
     * @param req EmbeddedRequest
     * @return Template the unclaimed template draft
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public TemplateDraft createEmbeddedTemplateDraft(EmbeddedRequest req) throws HelloSignException {
        return new TemplateDraft(httpClient.withAuth(auth).withPostFields(req.getPostFields())
                .post(BASE_URI + TEMPLATE_CREATE_EMBEDDED_DRAFT_URI).asJson());
    }

    /**
     * Retrieves the API app configuration for the given Client ID.
     * 
     * @param clientId String
     * @return ApiApp
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public ApiApp getApiApp(String clientId) throws HelloSignException {
        String url = BASE_URI + API_APP_URI + "/" + clientId;
        return new ApiApp(httpClient.withAuth(auth).get(url).asJson());
    }

    /**
     * Retrieves a paged list of API apps for the authenticated account.
     * 
     * @return ApiAppList
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public ApiAppList getApiApps() throws HelloSignException {
        return new ApiAppList(httpClient.withAuth(auth).get(BASE_URI + API_APP_LIST_URI).asJson());
    }

    /**
     * Creates a new ApiApp using the properties set on the provided ApiApp.
     * 
     * @param app ApiApp
     * @return ApiApp newly created ApiApp
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public ApiApp createApiApp(ApiApp app) throws HelloSignException {
        return new ApiApp(
                httpClient.withAuth(auth).withPostFields(app.getPostFields()).post(BASE_URI + API_APP_URI).asJson());
    }

    /**
     * Attempts to delete the API app with the given client ID.
     * 
     * @param clientId String The Client ID of the app that should be deleted.
     * @return boolean true if the API app was successfully deleted
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public boolean deleteApiApp(String clientId) throws HelloSignException {
        String url = BASE_URI + API_APP_URI + "/" + clientId;
        return HttpURLConnection.HTTP_NO_CONTENT == httpClient.withAuth(auth).delete(url).asHttpCode();
    }

    /**
     * Updates the API app with the given ApiApp object properties.
     * 
     * @param app ApiApp
     * @return ApiApp updated ApiApp
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or the JSON response.
     */
    public ApiApp updateApiApp(ApiApp app) throws HelloSignException {
        if (!app.hasClientId()) {
            throw new HelloSignException("Cannot update an ApiApp without a client ID. Create one first!");
        }
        String url = BASE_URI + API_APP_URI + "/" + app.getClientId();
        return new ApiApp(httpClient.withAuth(auth).withPostFields(app.getPostFields()).post(url).asJson());
    }

    /**
     * Performs an OPTIONS call to the HelloSign API to see if it's online.
     * 
     * @return true if HelloSign is available and the client is online, false
     *         otherwise.
     * @throws HelloSignException thrown if there's a problem processing the
     *         HTTP request or response.
     * @deprecated This function was previously for internal testing use only
     *         and is no longer operational. This will be removed in the next
     *         minor version of the SDK.
     */
    public boolean isOnline() throws HelloSignException {
        return true;
    }

    /**
     * Sets the access token for the OAuth user that this client will use to
     * perform requests.
     * 
     * @param accessToken String access token
     * @param tokenType String token type
     * @throws HelloSignException thrown if there's a problem setting the access
     *         token.
     */
    public void setAccessToken(String accessToken, String tokenType) throws HelloSignException {
        auth.setAccessToken(accessToken, tokenType);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy