GitHub – stylemessiah/GPay-SQLite-Fix: A magisk module to allow Google Pay to work on rooted phones

Верстка

Первое, о чем стоит сказать — предупредите дизайнеров о

. Если кратко по пунктам:

Код


Чтобы оплата через Google работала, на телефоне пользователя должны быть установлены Google Play Services версии не ниже 11.4. Но не беспокойтесь, есть специальный метод, который подскажет, можно ли провести оплату или же стоит спрятать кнопку.

Для начала добавим нужные зависимости в build.gradle уровня приложения. Перед внедрением проверяйте актуальность версий!

dependencies {
    compile 'com.google.android.gms:play-services-wallet:11.4.0'
    compile 'com.android.support:support-v4:24.1.1'
    compile 'com.android.support:appcompat-v7:24.1.1'
}

Далее следует обновить AndroidManifest:

Теперь осталось совсем чуть-чуть:

  • Создаём PaymentsClient в вашей Activity или в Fragment. Чтобы не захламлять эти классы, можно вынести весь код в методы GooglePaymentUtils, например. Тогда:
    class MainActivity : AppCompatActivity() {
        private lateinit var googlePaymentsClient: PaymentsClient
        ...
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            ...
            googlePaymentsClient = GooglePaymentUtils.createGoogleApiClientForPay(context)
        }
        ...
    }
    
    object GooglePaymentUtils {
        fun createGoogleApiClientForPay(context: Context): PaymentsClient =
            Wallet.getPaymentsClient(context, 
                                     Wallet.WalletOptions.Builder()
                                          .setEnvironment(WalletConstants.ENVIRONMENT_TEST)
                                          .setTheme(WalletConstants.THEME_LIGHT)
                                          .build())
    
    }
    

    Обратите внимание на константы:

    WalletConstants.ENVIRONMENT_TEST — пока Google не разрешит выход в боевую среду, вы должны использовать именно её, чтобы самостоятельно протестировать флоу оплаты. Не пугайтесь, когда увидите предупреждение на диалоге Google Pay, что приложение не опознано.
    WalletConstants.THEME_LIGHT — светлая тема диалога, также есть темная.

  • Отлично, у нас есть клиент, теперь мы готовы сделать запрос, можно ли вообще использовать оплату и показывать кнопку.
    object GooglePaymentUtils {
        fun checkIsReadyGooglePay(paymentsClient: PaymentsClient, 
                                  callback: (res: Boolean) -> Unit) {
            val isReadyRequest = IsReadyToPayRequest.newBuilder()
                      .addAllowedPaymentMethod(WalletConstants.PAYMENT_METHOD_CARD)
                      .addAllowedPaymentMethod(WalletConstants.PAYMENT_METHOD_TOKENIZED_CARD)
                      .build()
           val task = paymentsClient.isReadyToPay(isReadyRequest)
           task.addOnCompleteListener {
                try {
                    if (it.getResult(ApiException::class.java))
                    // можем показать кнопку оплаты, все хорошо
                        callback.invoke(true)
                    else
                    // должны спрятать кнопку оплаты
                        callback.invoke(false)
                } catch (e: ApiException) {
                    e.printStackTrace()
                    callback.invoke(false)
                }
            }
        }
    }
    

    PAYMENT_METHOD_CARD, PAYMENT_METHOD_TOKENIZED_CARD — говорят, что мы хотим видеть карточки из Google аккаунта пользователя и карточки, привязанные к Android Pay.

  • Если мы можем показать кнопку, значит, мы должны повесить на нее обработчик нажатий
    btnPaymentByGoogle.setOnClickListener {
        val request = GooglePaymentUtils.createPaymentDataRequest(price)
        AutoResolveHelper.resolveTask<PaymentData>(googlePaymentsClient.loadPaymentData(request),
                                                       context,
                                                       REQUEST_CODE)
    }
    

    Тут запомните, что price — это строчка. И самое важное, даже если вы вызываете AutoResolveHelper.resolveTask из фрагмента, то результат все-равно придет в активити (об этом чуть позже) [на момент написания статьи работает именно так, AutoResolveHelper не умеет возвращать результат во фрагмент].

    fun createPaymentDataRequest(price: String): PaymentDataRequest {
            val transaction = createTransaction(price)
            val request = generatePaymentRequest(transaction)
            return request
    }
    
    fun createTransaction(price: String): TransactionInfo =
                TransactionInfo.newBuilder()
                        .setTotalPriceStatus(WalletConstants.TOTAL_PRICE_STATUS_FINAL)
                        .setTotalPrice(price)
                        .setCurrencyCode(CURRENCY_CODE)
                        .build()
    
    private fun generatePaymentRequest(transactionInfo: TransactionInfo): PaymentDataRequest {
        val tokenParams = PaymentMethodTokenizationParameters
                                              .newBuilder() 
                                              .setPaymentMethodTokenizationType
    (WalletConstants.PAYMENT_METHOD_TOKENIZATION_TYPE_DIRECT)
                                              .addParameter("publicKey", TOKENIZATION_PUBLIC_KEY)
                                               build()
    
        return PaymentDataRequest.newBuilder()
                                 .setPhoneNumberRequired(false)
                                 .setEmailRequired(true)
                                 .setShippingAddressRequired(true)
                                 .setTransactionInfo(transactionInfo)
                                 .addAllowedPaymentMethods(SUPPORTED_METHODS)
                                 .setCardRequirements(CardRequirements.newBuilder()
                                                 .addAllowedCardNetworks(SUPPORTED_NETWORKS)
                                                 .setAllowPrepaidCards(true)
                                                 .setBillingAddressRequired(true)
                                                 .setBillingAddressFormat(WalletConstants.BILLING_ADDRESS_FORMAT_FULL)
                                                 .build())
                                .setPaymentMethodTokenizationParameters(tokenParams)
                                .setUiRequired(true)
                                .build()
        }
    
    

    Тут CURRENCY_CODE = “RUB”.
    WalletConstants.TOTAL_PRICE_STATUS_FINAL — говорим, что стоимость покупки окончательная и больше изменяться не будет.

    Также есть варианты:
    WalletConstants.TOTAL_PRICE_STATUS_ESTIMATED — стоимость примерная, и может измениться, например, после уточнения адреса.
    WalletConstants.TOTAL_PRICE_STATUS_NOT_CURRENTLY_KNOWN — еще не знаем, какая стоимость.

    Не могу сказать, как на практике поведут себя последние две константы, так как не проверял ¯_(ツ)_/¯.

    Остановимся на PaymentMethodTokenizationParameters и его методе setPaymentMethodTokenizationType:

    1. PAYMENT_METHOD_TOKENIZATION_TYPE_PAYMENT_GATEWAY
      используется только если ваш payment processor в списке:

      Adyen
      Braintree
      PaySafe
      Stripe
      Vantiv
      WorldPay

      Тогда вместо .addParameter("publicKey", TOKENIZATION_PUBLIC_KEY)
      вы должны написать .addParameter("gateway", "yourGateway")
      .addParameter("gatewayMerchantId", "yourMerchantIdGivenFromYourGateway")

    2. Иначе используется вышеуказанный тип PAYMENT_METHOD_TOKENIZATION_TYPE_DIRECT.
      Для этого вам необходимо запросить у провайдера платежных сервисов публичный ключ и передавать именно его в .addParameter("publicKey", TOKENIZATION_PUBLIC_KEY)

    Теперь остается создать запрос.
    .setPhoneNumberRequired — должен ли пользователь ввести номер.
    .setEmailRequired — должен ли пользователь ввести email
    .setShippingAddressRequired — должен ли пользователь выбрать страну. Тут можно ограничить число стран, для которых данная транзакция выполнится.
    .addAllowedPaymentMethods — у нас это WalletConstants.PAYMENT_METHOD_CARD — карты из google аккаунта, WalletConstants.PAYMENT_METHOD_TOKENIZED_CARD — карты, добавленные в Google Pay.

    В CardRequirements мы указываем, что должны работать карточки систем Visa, Mastercard и других (МИР, например)

    val SUPPORTED_NETWORKS = arrayListOf(WalletConstants.CARD_NETWORK_OTHER,
                                             WalletConstants.CARD_NETWORK_VISA,
                                             WalletConstants.CARD_NETWORK_MASTERCARD)

  • Все, мы создали запрос, отправили его через клиента и ждем результат через AutoResolveHelper.

    Как вы помните, результат придет в активити.

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
            super.onActivityResult(requestCode, resultCode, data)
            
            when (requestCode) {
                LOAD_PAYMENT_DATA_REQUEST_CODE -> {
                    when (resultCode) {
                        Activity.RESULT_OK -> {
                                if (data == null)
                                    return
                           
                                val paymentData = PaymentData.getFromIntent(data)
                        }
                        Activity.RESULT_CANCELED -> {
                            // Пользователь нажал назад, 
                            // когда был показан диалог google pay
                            // если показывали загрузку или что-то еще, 
                            // можете отменить здесь
                        }
                        AutoResolveHelper.RESULT_ERROR -> {
                            if (data == null)
                                return
                            
                            // Гугл сам покажет диалог ошибки. 
                            // Можете вывести логи и спрятать загрузку,
                            // если показывали
                            val status = AutoResolveHelper.getStatusFromIntent(data)
                            Log.e("GOOGLE PAY", "Load payment data has failed with status: $status")
                        }
                        else -> { }
                    }
                }    
                else -> { }
            }
        }
    


Вот и все, в paymentData у вас будет токен, который следует отдать вашему серверу. Дальнейшая логика зависит от вашего payment processor.

Тестирование

Ничего сложного, просто проверяете, что установлена константа

WalletConstants.ENVIRONMENT_TEST

, и проходите весь флоу. Списание денег с карточки производиться не будет, вам будет отдаваться тестовый токен, поэтому payment processor должен отклонить оплату.

Отправка на ручную проверку


Поздравляю! Вы готовы отправить свой дебаг билд на ручную проверку в Google.

Несколько советов:

Отправляете билд на

Релиз


Вам сказали, что все хорошо и можно выпускать приложение. Первым делом вас попросят

(с аккаунта продавца (merchant)).

Далее вас могут попросить прислать PCI Compliance. Эти документы подтверждают, что ваш payment processor соответствует стандартам безопасности по работе с картами. Запрашиваете у него и отправляете в поддержку.

Как только вы выполнили эти два пункта, вам скажут, что можно поменять WalletConstants.ENVIRONMENT_TEST на WalletConstants.ENVIRONMENT_PRODUCTION. Также может потребоваться поменять TOKENIZATION_PUBLIC_KEY, если вы использовали ключ с тестовой среды вашего payment processor.

Вот и все, теперь протестируйте реальную оплату и можете выпускать релиз в маркет!

Спасибо и удачи!

Project stats

New Alt-Repo Stats:

Original Repo Stats (before repo was erased recently):

GitHub All Releases: 35k

The gpay (google pay) sql fix features the work of:

  • BostonDan (original method)
  • braveheartleo – for the chattr suggestion (removed in later versions)
  • Didgeridoohan – for the working method to hide Google Play Services via magiskhide in a script
    loop & again during switch to MMM-EXT module format
  • Zackptg5 – for the MMM-EXT module format and help switching to it
  • Skittles9823 – for help during switch to MMM-EXT module format
  • jcmm11 – for help during switch to MMM-EXT module format
  • osm0sis – for help with adding magiskhide or zygisk denylist detection

Please note: the included LICENSE only covers the module components provided by the excellent work of Zack5tpg’s
Magisk Module Extended, which is available for here for module creators

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *