docusign / mobile-android-sdk

The Official Docusign Android SDK for integrating e-signature & signing documents. Click the link below to view SDK API Documentation https://docusign.github.io/mobile-android-sdk/
Other
16 stars 12 forks source link

How to implement Captive Signing in Android? #21

Closed sharadhihosamani closed 2 years ago

sharadhihosamani commented 3 years ago

@naveentds

How can we achieve captive signing in android ? I see an interface "DSCaptiveSigningListener". Can you please provide some details and snippet on how to utilise this.

Note: In IOS there is a dedicated method to launch captive signing session to the user: DSMEnvelopesManager().presentCaptiveSigning.

Regards, Sharadhi Hosamani

skongara commented 3 years ago

@sharadhihosamani Below is the code to implement Captive Signing for an envelope with envelopeId:

        val envelopeDelegate = DocuSign.getInstance().getEnvelopeDelegate()
        envelopeDelegate.getEnvelope(envelopeId, object : DSGetEnvelopeListener {
            override fun onSuccess(envelope: DSEnvelope) {
                val captiveSigningRecipients = envelope.recipients?.filter { recipient ->
                    (recipient.status == RecipientStatus.CREATED ||
                            recipient.status == RecipientStatus.SENT) && recipient.clientUserId != null
                }
                if (captiveSigningRecipients.isNullOrEmpty()) {
                    Toast.makeText(applicationContext, "No valid captive signing recipients", Toast.LENGTH_LONG).show()
                    return
                }
                val captiveSigningRecipient = captiveSigningRecipients.first()
                val signingDelegate = DocuSign.getInstance().getSigningDelegate()
                signingDelegate.launchCaptiveSigning(this@SigningActivity,
                        envelope.envelopeId,
                        captiveSigningRecipient.clientUserId!!,
                        object : DSCaptiveSigningListener {
                            override fun onStart(envelopeId: String) {
                                Log.d(TAG, "Captive Signing success for envelope: $envelopeId")
                            }

                            override fun onRecipientSigningSuccess(envelopeId: String, recipientId: String) {
                                Log.d(TAG, "Captive Signing success for recipient: $recipientId")
                            }

                            override fun onRecipientSigningError(envelopeId: String, recipientId: String, exception: DSSigningException) {
                                Log.d(TAG, "Captive Signing Error for recipient: $recipientId error: ${exception.message}")
                            }

                            override fun onSuccess(envelopeId: String) {
                                Log.d(TAG, "Captive Signing Completed")
                                Toast.makeText(applicationContext,
                                        "Envelope with $envelopeId signed successfully",
                                        Toast.LENGTH_LONG).show()
                            }

                            override fun onCancel(envelopeId: String, recipientId: String) {
                                Log.d(TAG, "Captive Signing Cancelled for recipient $recipientId")
                                Toast.makeText(applicationContext,
                                        "Signing of envelope $envelopeId is canceled",
                                        Toast.LENGTH_LONG).show()
                            }

                            override fun onError(envelopeId: String?, exception: DSSigningException) {
                                Log.d(TAG, "Error occurred while Captive Signing error: ${exception.message}")
                                Toast.makeText(applicationContext, exception.message, Toast.LENGTH_LONG)
                                        .show()
                            }

                        })
            }

            override fun onError(exception: DSEnvelopeException) {
                Log.d(TAG, "Error occurred while Captive Signing error: ${exception.message}")
                Toast.makeText(applicationContext, exception.message, Toast.LENGTH_LONG)
                        .show()
            }
        })

Please let me know if you have any questions.

Thanks, Shanthi

sharadhihosamani commented 3 years ago

@skongara

Thanks for the quick response. However, I'm getting below error when implemented above code: "com.docusign.androidsdk.exceptions.DSEnvelopeException: The envelope specified either does not exist or you have no rights to it"

Note: I am using getEnvelopeDelegate().composeAndSendEnvelope() method to create an envelope using a local document. I am passing the envelopeid returned from this method to the launchCaptiveSigning() function.

Regards, Sharadhi Hosamani

skongara commented 3 years ago

@sharadhihosamani
getEnvelopeDelegate().composeAndSendEnvelope() method creates an envelope locally (only on device) to be used for offline signing. For Captive Signing, envelope should exist on server.

To create envelope for Captive Signing: Create an envelope using envelopesPostEnvelopes() REST API and when you are creating recipients in the envelopeDefinition, set the signer's clientUserId. Below is the example to create an envelope for Captive Signing:

Create Envelope:

val envelopeDefinition = EnvelopeUtils.buildCachedEnvelopeDefinition(requireActivity())

    val envelopesApi =
        DocuSign.getInstance().getESignApiDelegate().createApiService(EnvelopesApi::class.java)

    val authenticationDelegate = DocuSign.getInstance().getAuthenticationDelegate()
    val user = authenticationDelegate.getLoggedInUser(context)
    val call = envelopesApi?.envelopesPostEnvelopes(
        user.accountId,
        null,
        null,
        null,
        null,
        null,
        envelopeDefinition
    )

    call?.enqueue(object : Callback<EnvelopeSummary> {
        override fun onResponse(
            call: Call<EnvelopeSummary>,
            response: Response<EnvelopeSummary>
        ) {
            if (response.isSuccessful) {
                // start Captive Signing
            }
        }

        override fun onFailure(call: Call<EnvelopeSummary>, t: Throwable) {
            Toast.makeText(context, t.message, Toast.LENGTH_LONG).show()
        }
    })

Building Envelope Definition:

    fun buildCachedEnvelopeDefinition(activity: Activity): EnvelopeDefinition? {
    val document = (activity.application as SDKSampleApplication).portfolioADoc

    val buffer: ByteArray = document?.readBytes()!!
    val envelopeDefinition = EnvelopeDefinition()
    envelopeDefinition.emailSubject = "Subject"
    val doc1 = Document()
    val doc1b64: String = Base64.encodeToString(buffer, Base64.DEFAULT)
    doc1.documentBase64 = doc1b64
    doc1.name = "Lorem Ipsum"
    doc1.fileExtension = "pdf"
    doc1.documentId = "3"
    doc1.order = "1"
    envelopeDefinition.documents = listOf(doc1)

    val user = DocuSign.getInstance().getAuthenticationDelegate().getLoggedInUser(activity)

    val signer1 = Signer()
    signer1.recipientId("1")
    signer1.clientUserId = "" // random string for client us
    signer1.signingGroupId = null
    signer1.signingGroupName = null
    signer1.canSignOffline = "true"
    signer1.routingOrder = "1"
    signer1.recipientType = DSRecipientType.SIGNER.name
    signer1.email = //"Signer email"
    signer1.name = // Signer name
    signer1.recipientId = "1"

    val signHere1 = SignHere()
    signHere1.documentId = "3"
    signHere1.recipientId = "1"
    signHere1.pageNumber = "1"
    signHere1.xPosition = "370"
    signHere1.yPosition = "110"

    val signer1Tabs = Tabs()
    signer1Tabs.signHereTabs = listOf(signHere1)
    signer1.tabs = signer1Tabs

    val recipients = Recipients()
    recipients.inPersonSigners = listOf(signer1)
    envelopeDefinition.recipients = recipients

    envelopeDefinition.is21CFRPart11 = "false"
    envelopeDefinition.signerCanSignOnMobile = "true"
    envelopeDefinition.status = "sent"
    envelopeDefinition.emailSubject = "Test Envelope Offline Signing"
    return envelopeDefinition
}

Note: Please make sure that the SignerMustHaveAccount or SignerMustLoginToSign property of the account settings is set to false for Captive Signing

Let me know if you have any questions.

Thanks, Shanthi

sharadhihosamani commented 3 years ago

This was very helpful @skongara . Thank you!

But when the captive signing was launched. The tool bar consisted of the hamburger menu and on tapping any of the links, I am getting redirected within the app(within signing session) and once the new link is opened..there is no way of going back to signing session. On tapping of hardware back button, a pop up comes with decline/continue option. Selecting continue is also not taking user back to signing screen, instead its stuck on the link that is launched.

Screenshot 2021-07-30 at 2 08 15 PM

However In iOS, the on tapping of those links, the url is launched outside the app in browser unlike android.

Regards, Sharadhi Hosamani

sharadhihosamani commented 3 years ago

@skongara Any information on this issue?

Thanks, Sharadhi Hosamani

naveentds commented 3 years ago

Hi @sharadhihosamani Thanks for letting us know about the issue. It looks like for some of the web content (navigated from links) in captive signing screen does not have close button associated. Hence, you were not be able to navigate back to signing screen. We will address this issue in out next SDK release.

Thanks Naveen

naveentds commented 2 years ago

@sharadhihosamani We released SDK 1.4.0. We added support to navigate back from the web content links in this release.

sharadhihosamani commented 2 years ago

Thanks so much for the update @naveentds