Android ödeme uygulaması geliştiricileri kılavuzu

Android ödeme uygulamanızı Web Ödemeleri ile çalışacak şekilde nasıl uyarlayacağınızı ve müşterilere daha iyi bir kullanıcı deneyimi sunacağınızı öğrenin.

Yayınlanma tarihi: 5 Mayıs 2020, son güncelleme tarihi: 27 Mayıs 2025

Payment Request API, web'e tarayıcı tabanlı yerleşik bir arayüz getirir. Bu arayüz, kullanıcıların gerekli ödeme bilgilerini her zamankinden daha kolay girmesine olanak tanır. API, platforma özgü ödeme uygulamalarını da çağırabilir.

Browser Support

  • Chrome: 60.
  • Edge: 15.
  • Firefox: behind a flag.
  • Safari: 11.1.

Source

Web Payments'ı kullanan platforma özel Google Pay uygulamasıyla ödeme adımları.

Web Ödemeleri, yalnızca Android Intent'leri kullanmaya kıyasla tarayıcı, güvenlik ve kullanıcı deneyimiyle daha iyi entegrasyon sağlar:

  • Ödeme uygulaması, satıcı web sitesi bağlamında modal olarak başlatılır.
  • Uygulama, mevcut ödeme uygulamanızı tamamlayıcı niteliktedir ve kullanıcı tabanınızdan yararlanmanızı sağlar.
  • Yan yüklemeyi önlemek için ödeme uygulamasının imzası kontrol edilir.
  • Ödeme uygulamaları birden fazla ödeme yöntemini destekleyebilir.
  • Kripto para birimi ve banka havaleleri gibi tüm ödeme yöntemleri entegre edilebilir. Android cihazlardaki ödeme uygulamaları, cihazdaki donanım çipine erişim gerektiren yöntemleri bile entegre edebilir.

Android ödeme uygulamasında Web Ödemeleri'ni uygulamak için dört adım gerekir:

  1. Satıcıların ödeme uygulamanızı keşfetmesini sağlayın.
  2. Müşterinin ödeme yapmaya hazır, kayıtlı bir ödeme aracı (ör. kredi kartı) olup olmadığını satıcıya bildirir.
  3. Müşterilerin ödeme yapmasına izin verin.
  4. Arayanın imza sertifikasını doğrulayın.

Web Payments'ın nasıl çalıştığını görmek için android-web-payment demosuna göz atın.

1. adım: Satıcıların ödeme uygulamanızı keşfetmesine izin verin

Web uygulaması manifestinde related_applications özelliğini Ödeme yöntemi ayarlama bölümündeki talimatlara göre ayarlayın.

Bir satıcının ödeme uygulamanızı kullanabilmesi için Payment Request API'yi kullanması ve ödeme yöntemi tanımlayıcısını kullanarak desteklediğiniz ödeme yöntemini belirtmesi gerekir.

Ödeme uygulamanıza özgü bir ödeme yöntemi tanımlayıcınız varsa tarayıcıların uygulamanızı keşfedebilmesi için kendi ödeme yöntemi manifestinizi oluşturabilirsiniz.

2. adım: Müşterinin kayıtlı ve ödeme yapmaya hazır bir ödeme aracı varsa satıcıyı bilgilendirin

Satıcı, hasEnrolledInstrument() numaralı telefonu arayarak müşterinin ödeme yapıp yapamayacağını sorgulayabilir. Bu sorguyu yanıtlamak için IS_READY_TO_PAY hizmetini Android hizmeti olarak uygulayabilirsiniz.

AndroidManifest.xml

Hizmetinizi, org.chromium.intent.action.IS_READY_TO_PAY işlemiyle bir intent filtresi kullanarak beyan edin.

<service
  android:name=".SampleIsReadyToPayService"
  android:exported="true">
  <intent-filter>
    <action android:name="org.chromium.intent.action.IS_READY_TO_PAY" />
  </intent-filter>
</service>

IS_READY_TO_PAY hizmeti isteğe bağlıdır. Ödeme uygulamasında böyle bir amaç işleyici yoksa web tarayıcısı, uygulamanın her zaman ödeme yapabileceğini varsayar.

AIDL

IS_READY_TO_PAY hizmetinin API'si AIDL'de tanımlanır. Aşağıdaki içeriklere sahip iki AIDL dosyası oluşturun:

org/chromium/IsReadyToPayServiceCallback.aidl

package org.chromium;

interface IsReadyToPayServiceCallback {
    oneway void handleIsReadyToPay(boolean isReadyToPay);
}

org/chromium/IsReadyToPayService.aidl

package org.chromium;

import org.chromium.IsReadyToPayServiceCallback;

interface IsReadyToPayService {
    oneway void isReadyToPay(IsReadyToPayServiceCallback callback, in Bundle parameters);
}

IsReadyToPayService kullanımı

IsReadyToPayService'nın en basit uygulaması aşağıdaki örnekte gösterilmektedir:

Kotlin

class SampleIsReadyToPayService : Service() {
    private val binder = object : IsReadyToPayService.Stub() {
        override fun isReadyToPay(callback: IsReadyToPayServiceCallback?, parameters: Bundle?) {
            callback?.handleIsReadyToPay(true)
        }
    }

    override fun onBind(intent: Intent?): IBinder? {
        return binder
    }
}

Java

import org.chromium.IsReadyToPayService;

public class SampleIsReadyToPayService extends Service {
    private final IsReadyToPayService.Stub mBinder =
        new IsReadyToPayService.Stub() {
            @Override
            public void isReadyToPay(IsReadyToPayServiceCallback callback, Bundle parameters) {
                if (callback != null) {
                    callback.handleIsReadyToPay(true);
                }
            }
        };

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

Yanıt

Hizmet, yanıtını handleIsReadyToPay(Boolean) yöntemini kullanarak gönderebilir.

Kotlin

callback?.handleIsReadyToPay(true)

Java

if (callback != null) {
    callback.handleIsReadyToPay(true);
}

İzin

Arayanın kim olduğunu kontrol etmek için Binder.getCallingUid() kullanabilirsiniz. Android OS hizmet bağlantısını önbelleğe alıp yeniden kullanabildiğinden onBind() yöntemi tetiklenmez. Bu nedenle, bu işlemi onBind yöntemiyle değil isReadyToPay yöntemiyle yapmanız gerekir.

Kotlin

override fun isReadyToPay(callback: IsReadyToPayServiceCallback?, parameters: Bundle?) {
    try {
        val untrustedPackageName = parameters?.getString("packageName")
        val actualPackageNames = packageManager.getPackagesForUid(Binder.getCallingUid())
        // ...

Java

@Override
public void isReadyToPay(IsReadyToPayServiceCallback callback, Bundle parameters) {
    try {
        String untrustedPackageName = parameters != null
                ? parameters.getString("packageName")
                : null;
        String[] actualPackageNames = packageManager.getPackagesForUid(Binder.getCallingUid());
        // ...

İşlemler Arası İletişim (IPC) çağrıları alırken giriş parametrelerini her zaman null için kontrol edin. Bu durum özellikle önemlidir. Çünkü Android OS'in farklı sürümleri veya çatalları beklenmedik şekillerde davranabilir ve ele alınmazsa hatalara yol açabilir.

packageManager.getPackagesForUid() genellikle tek bir öğe döndürse de kodunuz, arayanın birden fazla paket adı kullandığı nadir senaryoyu işlemelidir. Bu, uygulamanızın sağlam kalmasını sağlar.

Arayan paketin doğru imzaya sahip olduğunu doğrulama hakkında bilgi için Arayanın imza sertifikasını doğrulama başlıklı makaleyi inceleyin.

Parametreler

parameters Paket, Chrome 139'da eklendi. Her zaman null ile karşılaştırılmalıdır.

Aşağıdaki parametreler, parameters paketinde hizmete iletilir:

  • packageName
  • methodNames
  • methodData
  • topLevelOrigin
  • paymentRequestOrigin
  • topLevelCertificateChain

packageName, Chrome 138'de eklendi. Bu parametrenin değerini kullanmadan önce Binder.getCallingUid() ile karşılaştırarak doğrulamanız gerekir. Bu doğrulama önemlidir. Çünkü parameters paketi arayanın tam kontrolü altındayken Binder.getCallingUid(), Android OS tarafından kontrol edilir.

topLevelCertificateChain, WebView'da ve genellikle yerel test için kullanılan http://localhost gibi https olmayan web sitelerinde null olur.

3. adım: Müşterinin ödeme yapmasına izin verin

Satıcı, müşterinin ödeme yapabilmesi için show() numaralı telefonu arayarak ödeme uygulamasını başlatır. Ödeme uygulaması, niyet parametrelerinde işlem bilgileri bulunan bir Android niyeti PAY kullanılarak çağrılır.

Ödeme uygulaması, ödeme uygulamasına özel olan ve tarayıcı için opak olan methodName ve details ile yanıt verir. Tarayıcı, details dizesini JSON dizesi seri kaldırma kullanarak satıcı için bir JavaScript sözlüğüne dönüştürür ancak bunun ötesinde herhangi bir geçerlilik zorlamaz. Tarayıcı, details değerini değiştirmez. Bu parametrenin değeri doğrudan satıcıya iletilir.

AndroidManifest.xml

PAY intent filtresine sahip etkinlik, uygulamanın varsayılan ödeme yöntemi tanımlayıcısını belirten <meta-data> etiketine sahip olmalıdır.

Birden fazla ödeme yöntemini desteklemek için <meta-data> kaynağı içeren bir <meta-data> etiketi ekleyin.<string-array>

<activity
  android:name=".PaymentActivity"
  android:theme="@style/Theme.SamplePay.Dialog">
  <intent-filter>
    <action android:name="org.chromium.intent.action.PAY" />
  </intent-filter>

  <meta-data
    android:name="org.chromium.default_payment_method_name"
    android:value="https://bobbucks.dev/pay" />
  <meta-data
    android:name="org.chromium.payment_method_names"
    android:resource="@array/chromium_payment_method_names" />
</activity>

android:resource, her biri geçerli ve mutlak bir URL olması gereken dizeler listesi olmalıdır. Bu URL'ler, burada gösterildiği gibi bir HTTPS şemasına sahip olmalıdır.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="chromium_payment_method_names">
        <item>https://alicepay.com/put/optional/path/here</item>
        <item>https://charliepay.com/put/optional/path/here</item>
    </string-array>
</resources>

Parametreler

Aşağıdaki parametreler, etkinliğe Intent ekstraları olarak iletilir:

  • methodNames
  • methodData
  • merchantName
  • topLevelOrigin
  • topLevelCertificateChain
  • paymentRequestOrigin
  • total
  • modifiers
  • paymentRequestId
  • paymentOptions
  • shippingOptions

Kotlin

val extras: Bundle? = getIntent()?.extras

Java

Bundle extras = getIntent() != null ? getIntent().getExtras() : null;

methodNames

Kullanılan yöntemlerin adları. Öğeler, methodData sözlüğündeki anahtarlardır. Ödeme uygulamasının desteklediği yöntemler şunlardır:

Kotlin

val methodNames: List<String>? = extras.getStringArrayList("methodNames")

Java

List<String> methodNames = extras.getStringArrayList("methodNames");

methodData

methodNames öğelerinin her birinden methodData öğesine yapılan eşleme.

Kotlin

val methodData: Bundle? = extras.getBundle("methodData")

Java

Bundle methodData = extras.getBundle("methodData");

merchantName

Satıcının ödeme sayfasının <title> HTML etiketinin içeriği (tarayıcının üst düzey göz atma bağlamı).

Kotlin

val merchantName: String? = extras.getString("merchantName")

Java

String merchantName = extras.getString("merchantName");

topLevelOrigin

Şemasız satıcı kaynağı (üst düzey göz atma bağlamının şemasız kaynağı). Örneğin, https://mystore.com/checkout, mystore.com olarak iletilir.

Kotlin

val topLevelOrigin: String? = extras.getString("topLevelOrigin")

Java

String topLevelOrigin = extras.getString("topLevelOrigin");

topLevelCertificateChain

Satıcının sertifika zinciri (üst düzey tarama bağlamının sertifika zinciri). WebView, localhost veya diskteki bir dosya için değer null'dır. Her Parcelable, certificate anahtarına ve bayt dizisi değerine sahip bir pakettir.

Kotlin

val topLevelCertificateChain: Array<Parcelable>? =
        extras.getParcelableArray("topLevelCertificateChain")
val list: List<ByteArray>? = topLevelCertificateChain?.mapNotNull { p ->
    (p as Bundle).getByteArray("certificate")
}

Java

Parcelable[] topLevelCertificateChain =
        extras.getParcelableArray("topLevelCertificateChain");
if (topLevelCertificateChain != null) {
    for (Parcelable p : topLevelCertificateChain) {
        if (p != null && p instanceof Bundle) {
            ((Bundle) p).getByteArray("certificate");
        }
    }
}

paymentRequestOrigin

JavaScript'te new PaymentRequest(methodData, details, options) oluşturucusunu çağıran iframe tarama bağlamının şemasız kaynağı. Oluşturucu üst düzey bağlamdan çağrıldıysa bu parametrenin değeri topLevelOrigin parametresinin değerine eşittir.

Kotlin

val paymentRequestOrigin: String? = extras.getString("paymentRequestOrigin")

Java

String paymentRequestOrigin = extras.getString("paymentRequestOrigin");

total

İşlemin toplam tutarını temsil eden JSON dizesi.

Kotlin

val total: String? = extras.getString("total")

Java

String total = extras.getString("total");

Dizenin örnek içeriğini aşağıda görebilirsiniz:

{"currency":"USD","value":"25.00"}

modifiers

JSON.stringify(details.modifiers) çıkışı. Burada details.modifiers yalnızca supportedMethods, data ve total değerlerini içerir.

paymentRequestId

"Anında ödeme" uygulamalarının işlem durumuyla ilişkilendirmesi gereken PaymentRequest.id alanı. Satıcı web siteleri, bant dışı işlem durumu için "push-payment" uygulamalarını sorgulamak üzere bu alanı kullanır.

Kotlin

val paymentRequestId: String? = extras.getString("paymentRequestId")

Java

String paymentRequestId = extras.getString("paymentRequestId");

Yanıt

Etkinlik, yanıtını RESULT_OK ile birlikte setResult üzerinden geri gönderebilir.

Kotlin

setResult(Activity.RESULT_OK, Intent().apply {
    putExtra("methodName", "https://bobbucks.dev/pay")
    putExtra("details", "{\"token\": \"put-some-data-here\"}")
})
finish()

Java

Intent result = new Intent();
Bundle extras = new Bundle();
extras.putString("methodName", "https://bobbucks.dev/pay");
extras.putString("details", "{\"token\": \"put-some-data-here\"}");
result.putExtras(extras);
setResult(Activity.RESULT_OK, result);
finish();

Intent ekstraları olarak iki parametre belirtmeniz gerekir:

  • methodName: Kullanılan yöntemin adı.
  • details: Satıcının işlemi tamamlaması için gerekli bilgileri içeren JSON dizesi. Başarı true ise details, JSON.parse(details)'nin başarılı olacağı şekilde oluşturulmalıdır. Döndürülmesi gereken veri yoksa bu dize "{}" olabilir. Bu dize, satıcı web sitesi tarafından boş bir JavaScript sözlüğü olarak alınır.

Kullanıcı, ödeme uygulamasında işlemi iptal ederse RESULT_CANCELED değerini iletebilirsiniz. Bu durumda request.show(), satıcı web sitesinde AbortError ile reddedilir ve kullanıcının iptal ettiği belirtilir.

Kotlin

setResult(Activity.RESULT_CANCELED)
finish()

Java

setResult(Activity.RESULT_CANCELED);
finish();

Chrome 149'dan itibaren aşağıdaki sonuç değerleri desteklenir:

Kotlin

Activity.RESULT_CANCELED // 0 (0x00000000)
Activity.RESULT_OK // -1 (0xffffffff)
const val INTERNAL_PAYMENT_APP_ERROR = Activity.RESULT_FIRST_USER // 1 (0x00000001)

Java

Activity.RESULT_CANCELED // 0 (0x00000000)
Activity.RESULT_OK // -1 (0xffffffff)
static final int INTERNAL_PAYMENT_APP_ERROR = Activity.RESULT_FIRST_USER; // 1 (0x00000001)

Ödeme uygulaması dahili bir hata nedeniyle başarısız olursa sonuç kodu olarak Activity.RESULT_FIRST_USER ileterek bunu belirtebilirsiniz.

INTERNAL_PAYMENT_APP_ERROR döndürülürse request.show(), satıcı web sitesinde OperationError ile reddedilir. Bu, ödeme uygulamasında bir hata olduğunu gösterir.

RESULT_CANCELED (0) kullanıcı iptali (AbortError neden olur) ve INTERNAL_PAYMENT_APP_ERROR (1) dahili uygulama hatası (OperationError neden olur) arasındaki bu ayrım, satıcıların daha iyi kullanıcı akışları oluşturmasına olanak tanır.

Kotlin

setResult(Activity.RESULT_FIRST_USER)
finish()

Java

setResult(Activity.RESULT_FIRST_USER);
finish();

Çağrılan ödeme uygulamasından alınan bir ödeme yanıtının etkinlik sonucu RESULT_OK olarak ayarlanırsa Chrome, ekstralarında boş olmayan methodName ve details olup olmadığını kontrol eder. Doğrulama başarısız olursa Chrome, geliştiriciye yönelik aşağıdaki hata mesajlarından biriyle request.show()'dan reddedilen bir söz döndürür:

'Payment app returned invalid response. Missing field "details".'
'Payment app returned invalid response. Missing field "methodName".'

İzin

Etkinlik, arayanı getCallingPackage() yöntemiyle kontrol edebilir.

Kotlin

val caller: String? = callingPackage

Java

String caller = getCallingPackage();

Son adım, arayan paketin doğru imzaya sahip olduğunu onaylamak için arayanın imza sertifikasını doğrulamaktır.

4. adım: Arayanın imza sertifikasını doğrulayın

Arayanın paket adını Binder.getCallingUid() içinde IS_READY_TO_PAY, Activity.getCallingPackage() içinde ise PAY ile kontrol edebilirsiniz. Arayanın gerçekten de düşündüğünüz tarayıcı olduğunu doğrulamak için imza sertifikasını kontrol etmeli ve doğru değerle eşleştiğinden emin olmalısınız.

API düzeyi 28 ve sonraki sürümleri hedefliyorsanız ve tek imzalama sertifikasına sahip bir tarayıcıyla entegrasyon yapıyorsanız PackageManager.hasSigningCertificate() kullanabilirsiniz.

Kotlin

val packageName: String =  // The caller's package name
val certificate: ByteArray =  // The correct signing certificate
val verified = packageManager.hasSigningCertificate(
        callingPackage,
        certificate,
        PackageManager.CERT_INPUT_SHA256
)

Java

String packageName =  // The caller's package name
byte[] certificate =  // The correct signing certificate
boolean verified = packageManager.hasSigningCertificate(
        callingPackage,
        certificate,
        PackageManager.CERT_INPUT_SHA256);

PackageManager.hasSigningCertificate(), sertifika rotasyonunu doğru şekilde işlediği için tek sertifikalı tarayıcılarda tercih edilir. (Chrome'un tek bir imzalama sertifikası vardır.) Birden fazla imzalama sertifikası olan uygulamalar bu sertifikaları döndüremez.

API düzeyi 27 ve öncesini desteklemeniz veya birden fazla imzalama sertifikası olan tarayıcıları işlemeniz gerekiyorsa PackageManager.GET_SIGNATURES kullanabilirsiniz.

Kotlin

val packageName: String =  // The caller's package name
val expected: Set<String> =  // The correct set of signing certificates

val packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
val sha256 = MessageDigest.getInstance("SHA-256")
val actual = packageInfo.signatures.map {
    SerializeByteArrayToString(sha256.digest(it.toByteArray()))
}
val verified = actual.equals(expected)

Java

String packageName  =  // The caller's package name
Set<String> expected =  // The correct set of signing certificates

PackageInfo packageInfo =
        packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
Set<String> actual = new HashSet<>();
for (Signature it : packageInfo.signatures) {
    actual.add(SerializeByteArrayToString(sha256.digest(it.toByteArray())));
}
boolean verified = actual.equals(expected);

Hata ayıkla

Hataları veya bilgilendirme mesajlarını gözlemlemek için aşağıdaki komutu kullanın:

adb logcat | grep -i pay