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.
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:
- Satıcıların ödeme uygulamanızı keşfetmesini sağlayın.
- Müşterinin ödeme yapmaya hazır, kayıtlı bir ödeme aracı (ör. kredi kartı) olup olmadığını satıcıya bildirir.
- Müşterilerin ödeme yapmasına izin verin.
- 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:
packageNamemethodNamesmethodDatatopLevelOriginpaymentRequestOrigintopLevelCertificateChain
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:
methodNamesmethodDatamerchantNametopLevelOrigintopLevelCertificateChainpaymentRequestOrigintotalmodifierspaymentRequestIdpaymentOptionsshippingOptions
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ıtrueisedetails,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