Enable Users to Accept Offer

This section describes how to build an offer click experience. This section comprises the following topics:

Enable Users to Accept Offer

Enable Users to Accept Offer

Enable Users to Accept Offer

Enable Users to Accept Offer

1.0. Integrate Using IQL adClickRedirection API

Submit the user’s personal information to the adClick Redirection Endpoint V3 (GET) endpoint. This endpoint is received in the button_click_url field of the adUnit v2 response. The button_click_url comes pre-populated with authentication information so no API KEY is required to call this endpoint. Therefore this API call can be directly made by the mobile clients to the IQL server without the intervention of the publisher’s backend.

Click here to read the documentation for the adClickRedirection API.

Refer to the following code snippets to use the adClickRedirection endpoint on the client side:

package com.zendrive.iqlref.ui.home.personalinformation.presentation
....
....

class PersonalInformationFragment : Fragment() {

    private val viewModel: PersonalInformationViewModel by viewModels()
    private lateinit var binding: FragmentPersonalInformationNewBinding
    ....
    ....
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        ....
        ....

        with(binding) {
            viewModel.getAdUnitDetails { adUnitResponse ->
                Timber.d("adUnitResponse: $adUnitResponse")
                adUnitResponse?.let {
                    insurerName = it.adUnit.insurerName
                    if (it.adUnit.legal.termsAndConditionsUrl.isNullOrEmpty()
                        .not() && it.adUnit.legal.privacyPolicyUrl.isNullOrEmpty().not()
                    ) {
                        termsAndCondition.makeVisible()
                        termsAndCondition.text = setSpanURL(it.adUnit)
                        termsAndCondition.enableMovementLinks()
                    }

                    it.adUnit.legal.disclaimer?.let {
                        if (it.generic.isNullOrEmpty().not()) {
                            disclaimer.setTextInTv(it.generic)
                        }
                    }

                    buttonClickUrl = it.adUnit.buttonClickUrl
                    viewModel.loadPersonalInformation()
                }
            }

            buttonConfirm.setOnClickListener {
                buttonClickUrl?.let { url ->
                    handleConfirmButtonClick(url)
                }
            }

            viewLifecycleOwner.lifecycleScope.launch {
                viewModel.description.collectLatest {
                    letsMakeSure.text = getString(
                        it,
                        insurerName
                    )
                }
            }
        }
    }
    ...
    ...
    ...

    private fun handleConfirmButtonClick(buttonClickUrl: String) {
        with(binding) {
            val firstName = firstNameEditText.extractString()
            val lastName = lastNameEditText.extractString()
            val email = emailEditText.extractTrimmedString()
            val dob = dobEditText.extractString()
            val streetInfo = streetEditText.extractString()
            val city = cityEditText.extractString()
            val stateCode = stateEditText.extractString()
            val zipCode = zipCodeEditText.extractString()

            viewModel.savePersonalInformation(
                firstName = firstName,
                lastName = lastName,
                email = email,
                dob = dob,
                streetInfo = streetInfo,
                city = city,
                stateCode = stateCode,
                zipCode = zipCode
            )

            val request = viewModel.createPersonalInformationRequestPayload(
                firstName = firstName,
                lastName = lastName,
                email = email,
                dob = dob,
                streetInfo = streetInfo,
                city = city,
                stateCode = stateCode,
                zipCode = zipCode
            )

            if (request != null) {
                val updatedButtonClickUrl =
                    viewModel.replacePayloadWithRequest(buttonClickUrl, request)
                Timber.d("Appended Get Url $updatedButtonClickUrl")

                if (isInternetConnected) {
                    InsurerWebsiteActivity.startInsurerWebsiteActivity(
                        activity = requireActivity(),
                        url = updatedButtonClickUrl
                    )
                    userNavigatedToInsurerPage = true
                } else {
                    DialogHelper.showNoInternetDialog(
                        weakActivity = WeakReference(requireActivity()),
                        onPositiveActionClicked = {
                            it.dismiss()
                        }
                    )
                }
            }
        }
    }

    ....
    ....
    ....
}

Use the following code snippets to create the payload for the request:

package com.zendrive.iqlref.ui.home.personalinformation.presentation

import android.content.Context
....
....
class PersonalInformationViewModel : ViewModel() {

    private val iQLApisRepository = ZWLCore.iqlApisRepository()
    private val savePersonalInformationUseCase = ZWLCore.savePersonalInformationUseCase()
    private val getPersonalInformationUseCase = ZWLCore.getPersonalInformationUseCase()
    ....
    ....

    /**
     * get ad unit details
     */
    fun getAdUnitDetails(callback: (AdUnitResponse?) -> Unit) {
        viewModelScope.launch(AppDispatchers.IO) {
            val adDetails = iQLApisRepository.getAdUnitDetails()
            viewModelScope.launch(Dispatchers.Main) {
                callback(adDetails)
            }
        }
    }

    fun replacePayloadWithRequest(url: String, encodedBase64Url: String): String {
        return url.replace(PAYLOAD_PLACEHOLDER, encodedBase64Url)
    }

    private fun toJsonString(personalInformation: PersonalInformation): String {
        return personalInformation.asJSON()
    }

    private fun convertToBase64String(jsonStringPayload: String): String {
        return jsonStringPayload.encodeUsingBase64()
    }

    fun createPersonalInformationRequestPayload(
        firstName: String,
        lastName: String,
        email: String,
        dob: String,
        streetInfo: String,
        city: String,
        stateCode: String,
        zipCode: String
    ): String? {
        val personalInformation =
            PersonalInformation(
                firstName = firstName,
                lastName = lastName,
                email = email,
                dateOfBirth = DateUtils.convertDateToOtherFormatFromGivenFormat(
                    fromFormat = DateUtils.MM_DD_YYYY,
                    toFormat = DateUtils.YYYY_MM_DD,
                    date = dob
                ),
                address = Address(
                    street = streetInfo,
                    city = city,
                    stateCode = stateCode,
                    zipCode = zipCode
                )
            )
        val jsonStringPayload = toJsonString(personalInformation)
        val base64Payload = convertToBase64String(jsonStringPayload)
        val urlEncoder = UrlUtils.encodeUrl(base64Payload)
        Timber.d("json payload $jsonStringPayload")
        Timber.d("base64Payload $base64Payload")
        Timber.d("urlEncoder $urlEncoder")
        return urlEncoder
    }
}

As detailed in the code snippets given above, create a base64 encoded payload with the user information provided by the user. Replace the <payload> string in the buttonClickURL field received in the adUnitDetails API with the generated payload and load it using Webview.

2.0. Enable Opening Of Offer In a Webview

Use the following code to enable the user to open an offer either in an in-app browser, or in webview mode:

The in-app browser uses Custom Tabs, a Chrome feature that most major Android browsers support.

2.1. Fallback for In-app Custom Tabs

If in-app custom tabs are not supported by your device browser, allow your users to use webview as the fallback mechanism to open the insurer’s webpage.

class InsurerWebsiteActivity : AppBaseActivity() {

   private lateinit var binding: ActivityInsurerWebsiteBinding
   private var shouldOverrideUrl = false

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       binding = ActivityInsurerWebsiteBinding.inflate(layoutInflater)
       setContentView(binding.root)
       supportActionBar?.setDisplayHomeAsUpEnabled(true)

       showProgress()
       intent.getStringExtra(URL_OBJECT)?.let {
           openUrlInWebView(it)
       } ?: run {
           Timber.e("button click url is either empty or null")
           Toast.makeText(this, getString(R.string.no_web_link_found), Toast.LENGTH_SHORT).show()
           hideProgress()
       }
       registerOnBackPressCallBack(enabled = true) {
           showExitDialog()
       }
   }

   private fun showProgress() {
       binding.progressBar.makeVisible()
   }

   private fun hideProgress() {
       binding.progressBar.makeGone()
   }

   private fun openUrlInWebView(url: String) {
       lifecycleScope.launch {
           with(binding) {
               webView.settings.allowFileAccess = false
               webView.settings.javaScriptEnabled = true
               binding.webView.webChromeClient = WebChromeClient()
               webView.webViewClient = object : WebViewClient() {
                   override fun onReceivedError(
                       view: WebView?,
                       request: WebResourceRequest?,
                       error: WebResourceError?
                   ) {
                       Timber.e(" Redirection API Error received: $error")
                   }

                   override fun onReceivedHttpError(
                       view: WebView?,
                       request: WebResourceRequest?,
                       errorResponse: WebResourceResponse?
                   ) {
                       super.onReceivedHttpError(view, request, errorResponse)
                       Timber.d("Redirection API Error onReceivedHttpError: $errorResponse ${errorResponse?.reasonPhrase}")
                   }

                   override fun onReceivedSslError(
                       view: WebView?,
                       handler: SslErrorHandler?,
                       error: SslError?
                   ) {
                       super.onReceivedSslError(view, handler, error)
                       Timber.d("onReceivedSslError")
                   }

                   override fun shouldOverrideUrlLoading(
                       webView: WebView,
                       request: WebResourceRequest
                   ): Boolean {
                       val uri = request.url
                       shouldOverrideUrl = true
                       return shouldOverrideUrlLoading(uri.toString(), webView)
                   }

                   private fun shouldOverrideUrlLoading(url: String, webView: WebView): Boolean {
                       Timber.d("shouldOverrideUrlLoading() URL : $url")
                       webView.loadUrl(url)
                       // Returning True means that application wants to leave the current WebView and handle the url itself, otherwise return false.
                       return true
                   }

                   override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
                       super.onPageStarted(view, url, favicon)
                       shouldOverrideUrl = false
                       showProgress()
                   }

                   override fun onPageFinished(view: WebView?, url: String?) {
                       super.onPageFinished(view, url)
                       view?.let {
                           val webViewTitle = it.title
                           setPageTitle(webViewTitle)
                           if (shouldOverrideUrl.not()) {
                               hideProgress()
                           }
                       }
                   }

                   private fun setPageTitle(webViewTitle: String?) {
                       supportActionBar?.let {
                           webViewTitle?.let { webTitle ->
                               it.title = webTitle
                           }
                       }
                   }
               }
               webView.loadUrl(url)
           }
       }
   }

   override fun onSupportNavigateUp(): Boolean {
       showExitDialog()
       return true
   }

   private fun showExitDialog() {
       val bottomSheetFragment = ExitInsurerWebsiteBottomSheetDialog.newInstance()
       bottomSheetFragment.show(supportFragmentManager, bottomSheetFragment.tag)
   }

   companion object {
       private const val URL_OBJECT = "url_object"

       @JvmStatic
       fun startInsurerWebsiteActivity(activity: FragmentActivity, url: String) {
           Intent(activity, InsurerWebsiteActivity::class.java).apply {
               putExtra(URL_OBJECT, url)
           }.also {
               activity.startActivity(it)
           }
       }
   }
}

3.0. IQL Reference Application UX Design

Here is the overall design implementation for the IQL Reference Application:

4.0. Publisher Integration Testing Checklist

Use the following testing checklist to ensure that the user is enabled to accept an offer:

  • When an offer is presented, ensure that the user is successfully directed to the insurer's website. Validate that the in-app browser or webview experience is working smoothly. Check that the user is able to successfully submit any missing details.