You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让同一ClientId重新签署Docusign.Click弹性签署协议

关于DocuSign Click弹性签署协议重新签署的实现问题

问题背景

  • 已实现基于DocuSign.Click的C#测试方案,支持通过Agreement-Id获取协议响应数据、通过Client-Id获取协议信息
  • 需求:向同一ClientId发送同一弹性签署协议完成重新签署,要求不删除该ClientId的历史协议、不新建弹性模板
  • 测试异常:修改传入模板的日期参数后,仅返回“已同意”响应,未生成新的协议链接

现有测试代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DocuSign.Click.Api;
using DocuSign.Click.Client;
using DocuSign.Click.Model;
using DocuSign.Click.Client.Auth;
using DocuSign.eSign.Model;
using static DocuSign.Click.Api.AccountsApi;

namespace DocuSignConsoleTester
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            string integrationKey = "xx..xx";
            string userId = "xx..xx";
            string accountId = "xx..xx";
            string authServer = "account-d.docusign.com"; // for demo environment
            string accountBaseURI = "https://demo.docusign.net/clickapi"; // for demo environment
            string fullName = "xx..xx";
            string email = "xyz@example.com";
            string company = "xx..xx";
            string title = "xx..xx";
            string clickwrapId = "xx..xx";
            string datenow = DateTime.Now.ToString();


            string access_token = GetJwtToken(integrationKey, userId, accountId, authServer);

            Console.WriteLine("Acess_Token: [" + access_token + "]");
            Console.WriteLine("\r\r");

            AccountsApi clickAccountApi = BuildHeader(accountBaseURI, access_token);

            UserAgreementRequest userAgreementRequest = BuildUpdateClickwrapHasAgreedRequest(fullName, email, company, title, datenow);

            ApiResponse<UserAgreementResponse> apiAgreement = CreateAgreement(clickAccountApi, accountId, clickwrapId, userAgreementRequest);

            string AgreedOn = apiAgreement.Data.AgreedOn.ToString();
            string agreementId = apiAgreement.Data.AgreementId;
            string agreementURL = apiAgreement.Data.AgreementUrl;
            string clientUserid = apiAgreement.Data.ClientUserId;
            string createdOn = apiAgreement.Data.CreatedOn.ToString();

            UserAgreementResponse AgreementInfo = GetAgreementInfo(clickAccountApi, accountId, clickwrapId, agreementId);

            List<UserAgreementResponse> ClientAgreementList = GetClientAgreementInfo(clickAccountApi, accountId, clickwrapId, agreementId, clientUserid);

            Console.WriteLine("API response data: ");
            Console.WriteLine(apiAgreement.Data.ToString());
            Console.WriteLine("\r\r");

            Console.WriteLine("Agreement response data: ");
            Console.WriteLine(AgreementInfo.ToString());
            Console.WriteLine("\r\r");

            Console.WriteLine("Client agreement data: ");
            foreach (UserAgreementResponse a in ClientAgreementList)
            {
                Console.WriteLine(a.ToString());
                Console.WriteLine("\r");
            }
            Console.WriteLine("\r\r");

            Console.WriteLine(" End of test run ");

        }

        public static string GetJwtToken(string clientId, string userId, string accountId, string authServer)
        {
            string oauthBasePath = authServer;
            int expiresInHours = 1;
            List<string> scopes = new List<string>() { "signature impersonation click.manage click.send" };
            DocuSignClient dsClient = new DocuSignClient(authServer);
            string rsaKeyBase64 = @"xx..xx==";
            var rsaKeyBytes = Convert.FromBase64String(rsaKeyBase64);
            OAuth.OAuthToken token = dsClient.RequestJWTUserToken(clientId, userId, oauthBasePath, rsaKeyBytes, expiresInHours, scopes);

            return token.access_token.ToString();
        }

        public static AccountsApi BuildHeader(string basePath, string accessToken)
        {
            var dsClient = new DocuSignClient(basePath);
            dsClient.Configuration.DefaultHeader.Add("Authorization", "Bearer " + accessToken);
            var clickAccountApi = new AccountsApi(dsClient);

            return clickAccountApi;
        }

        public static UserAgreementRequest BuildUpdateClickwrapHasAgreedRequest(string fullName, string email, string company, string title, string date)
        {
            var userAgreementRequest = new UserAgreementRequest { DocumentData = new Dictionary<string, string>() };
            userAgreementRequest.DocumentData.Add("fullName", fullName);
            userAgreementRequest.DocumentData.Add("email", email);
            userAgreementRequest.DocumentData.Add("company", company);
            userAgreementRequest.DocumentData.Add("title", title);
            userAgreementRequest.DocumentData.Add("date", date);

            userAgreementRequest.ClientUserId = email;

            return userAgreementRequest;
        }

        public static ApiResponse<UserAgreementResponse> CreateAgreement(AccountsApi clickAccountApi, string accountId, string clickwrapId, UserAgreementRequest userAgreementRequest)
        {
            ApiResponse<UserAgreementResponse> response = clickAccountApi.CreateHasAgreedWithHttpInfo(accountId, clickwrapId, userAgreementRequest);

            if (response.StatusCode == 201)
            {
                return response;
            }
            else
            {
                response.Data.AgreementUrl = "Already Agreed";
                return response;
            }

        }

        public static UserAgreementResponse GetAgreementInfo(AccountsApi clickAccountApi, string accountId, string clickwrapId, string agreementId)
        {
            UserAgreementResponse response = clickAccountApi.GetAgreement(accountId, clickwrapId, agreementId);

            return response;
        }

        public static List<UserAgreementResponse> GetClientAgreementInfo(AccountsApi clickAccountApi, string accountId, string clickwrapId, 
                                                                        string agreementId, string clientId)
        {
            GetClickwrapAgreementsOptions gcao = new GetClickwrapAgreementsOptions();
            gcao.clientUserId = clientId;
            ClickwrapAgreementsResponse acctResponse = clickAccountApi.GetClickwrapAgreements(accountId, clickwrapId, gcao);
            List<UserAgreementResponse> usrAgreements = acctResponse.UserAgreements.ToList();

            return usrAgreements;
        }

    }
}

解决方案(无需删除历史协议/新建模板)

核心逻辑

DocuSign Click的CreateHasAgreed接口默认会检查ClientUserId与Clickwrap的关联状态,若已存在有效同意记录则直接返回已同意状态。要触发重新签署,需通过ForceNewAgreement参数强制生成新的同意记录。

具体修改

  1. 添加强制生成新协议的参数
    修改BuildUpdateClickwrapHasAgreedRequest方法,新增ForceNewAgreement = true配置:

    public static UserAgreementRequest BuildUpdateClickwrapHasAgreedRequest(string fullName, string email, string company, string title, string date)
    {
        var userAgreementRequest = new UserAgreementRequest { DocumentData = new Dictionary<string, string>() };
        userAgreementRequest.DocumentData.Add("fullName", fullName);
        userAgreementRequest.DocumentData.Add("email", email);
        userAgreementRequest.DocumentData.Add("company", company);
        userAgreementRequest.DocumentData.Add("title", title);
        userAgreementRequest.DocumentData.Add("date", date);
    
        userAgreementRequest.ClientUserId = email;
        // 强制生成新协议,保留历史记录
        userAgreementRequest.ForceNewAgreement = true;
    
        return userAgreementRequest;
    }
    
  2. 调整响应处理逻辑
    设置ForceNewAgreement=true后,即使ClientUserId已有历史协议,接口也会返回201状态码并生成新的协议链接,可简化响应处理:

    public static ApiResponse<UserAgreementResponse> CreateAgreement(AccountsApi clickAccountApi, string accountId, string clickwrapId, UserAgreementRequest userAgreementRequest)
    {
        ApiResponse<UserAgreementResponse> response = clickAccountApi.CreateHasAgreedWithHttpInfo(accountId, clickwrapId, userAgreementRequest);
        // 强制生成新协议后,合法请求均会返回201,新链接在response.Data.AgreementUrl中
        return response;
    }
    

关键说明

  • ForceNewAgreement参数是核心,它会在保留历史协议的前提下,为同一ClientUserId创建新的同意记录
  • 历史协议可通过GetClickwrapAgreements接口正常查询
  • 修改模板参数(如日期)配合该参数,会生成包含最新参数的新协议链接,用户签署后新增一条同意记录

内容的提问来源于stack exchange,提问作者DigDug

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 18:40:00