androidkotlinandroid-jetpack-composegoogle-pay

Google pay Api in Jetpack compose - How to


I can't find resources on the internet on How to achieve the google API integration into an Compose based app.

I need help, especially in the AutoResolveHelper.resolveTask, how to do it in compose.

Thank you for your answers.

(That's mind blowing that there is no more documentation on this API, it's pretty difficult to implement).

Edit :

This is the traditional way to do it ->

private fun requestPayment() {

    // Disables the button to prevent multiple clicks.
    googlePayButton.isClickable = false

    // The price provided to the API should include taxes and shipping.
    // This price is not displayed to the user.
    val garmentPrice = selectedGarment.getDouble("price")
    val priceCents = Math.round(garmentPrice * PaymentsUtil.CENTS.toLong()) + SHIPPING_COST_CENTS

    val paymentDataRequestJson = PaymentsUtil.getPaymentDataRequest(priceCents)
    if (paymentDataRequestJson == null) {
        Log.e("RequestPayment", "Can't fetch payment data request")
        return
    }
    val request = PaymentDataRequest.fromJson(paymentDataRequestJson.toString())

    // Since loadPaymentData may show the UI asking the user to select a payment method, we use
    // AutoResolveHelper to wait for the user interacting with it. Once completed,
    // onActivityResult will be called with the result.
    if (request != null) {
        AutoResolveHelper.resolveTask(
                paymentsClient.loadPaymentData(request), this, LOAD_PAYMENT_DATA_REQUEST_CODE)
    }
}

/**
 * Handle a resolved activity from the Google Pay payment sheet.
 *
 * @param requestCode Request code originally supplied to AutoResolveHelper in requestPayment().
 * @param resultCode Result code returned by the Google Pay API.
 * @param data Intent from the Google Pay API containing payment or error data.
 * @see [Getting a result
 * from an Activity](https://developer.android.com/training/basics/intents/result)
 */
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    when (requestCode) {
        // Value passed in AutoResolveHelper
        LOAD_PAYMENT_DATA_REQUEST_CODE -> {
            when (resultCode) {
                RESULT_OK ->
                    data?.let { intent ->
                        PaymentData.getFromIntent(intent)?.let(::handlePaymentSuccess)
                    }

                RESULT_CANCELED -> {
                    // The user cancelled the payment attempt
                }

                AutoResolveHelper.RESULT_ERROR -> {
                    AutoResolveHelper.getStatusFromIntent(data)?.let {
                        handleError(it.statusCode)
                    }
                }
            }

            // Re-enables the Google Pay payment button.
            googlePayButton.isClickable = true
        }
    }
}

Solution

  • UPDATE

    I see that now, there's a way of doing the below code in a much simpler way.

    Based on this tutorial, we can now use

    registerForActivityResult(GetPaymentDataResult())
    

    which will provide us with most of the logic we need. Full example:

    private val paymentDataLauncher =
        registerForActivityResult(GetPaymentDataResult()) { taskResult ->
            when (taskResult.status.statusCode) {
                CommonStatusCodes.SUCCESS -> {
                    taskResult.result!!.let {
                        Log.i("Google Pay result:", it.toJson())
                        model.setPaymentData(it)
                    }
                }
                //CommonStatusCodes.CANCELED -> The user canceled
                //AutoResolveHelper.RESULT_ERROR -> The API returned an error (it.status: Status)
                //CommonStatusCodes.INTERNAL_ERROR -> Handle other unexpected errors
            }
        }
    

    And then we call this launcher like

    val task = model.getLoadPaymentDataTask(priceCents = 1000L)
    task.addOnCompleteListener(paymentDataLauncher::launch)
    

    ---- ORIGINAL ANSWER

    I recently came into this exact same issue. I didn't want to add the code to the activity and make an ugly, unreadable code so I ended up doing this:

    In your composable, add this code to the click modifier or whatever:

    val task = paymentsClient.loadPaymentData(request)
    task.addOnCompleteListener { completedTask ->
       if (completedTask.isSuccessful) {
          completedTask.result.let{
             //Do whatever you want
          }
       } else {
          when (val exception = completedTask.exception) {
             is ResolvableApiException -> {
                resolvePaymentForResult.launch(
                    IntentSenderRequest.Builder(exception.resolution).build()
                )
             }
             is ApiException -> {
                Log.e("Google Pay API error", "Error code: ${exception.statusCode}, Message: ${exception.message}")
             }
             else -> {
                Log.e("Unexpected non API exception")
             }
          }
       }
    }
    

    With resolvePaymentForResult being:

    val resolvePaymentForResult = rememberLauncherForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
            result: ActivityResult ->
        when (result.resultCode) {
            RESULT_OK ->
                result.data?.let { intent ->
                    PaymentData.getFromIntent(intent)?.let{
                       //Do whatever you want
                    }
                }
    
            RESULT_CANCELED -> {
                // The user cancelled the payment attempt
            }
        }
    }
    

    You can always move paymentsClient.loadPaymentData(request) to your ViewModel if that's your architecture too!

    Hope that will clean up your code a little bit more :)