بیاموزید که چگونه برنامه پرداخت Android خود را با Web Payments سازگار کنید و تجربه کاربری بهتری برای مشتریان فراهم کنید.
تاریخ انتشار: 5 می 2020، آخرین به روز رسانی: 27 می 2025
Payment Request API یک رابط داخلی مبتنی بر مرورگر را به وب میآورد که به کاربران اجازه میدهد اطلاعات پرداخت مورد نیاز را راحتتر از همیشه وارد کنند. API همچنین می تواند برنامه های پرداخت ویژه پلتفرم را فراخوانی کند.
در مقایسه با استفاده از Intent Android، Web Payments امکان ادغام بهتر با مرورگر، امنیت و تجربه کاربر را فراهم می کند:
- برنامه پرداخت به صورت مودال در زمینه وب سایت تاجر راه اندازی شده است.
- پیاده سازی مکمل برنامه پرداخت موجود شما است و به شما امکان می دهد از پایگاه کاربری خود استفاده کنید.
- برای جلوگیری از بارگذاری جانبی، امضای برنامه پرداخت بررسی میشود.
- برنامه های پرداخت می توانند چندین روش پرداخت را پشتیبانی کنند.
- هر روش پرداخت مانند ارز دیجیتال، انتقال بانکی و غیره را می توان یکپارچه کرد. برنامههای پرداخت در دستگاههای اندرویدی حتی میتوانند روشهایی را که نیاز به دسترسی به تراشه سختافزاری روی دستگاه دارند، ادغام کنند.
برای پیاده سازی Web Payments در یک برنامه پرداخت Android، چهار مرحله لازم است:
- به تاجران اجازه دهید برنامه پرداخت شما را کشف کنند.
- اگر مشتری ابزار ثبت نامی (مانند کارت اعتباری) آماده پرداخت را دارد، به تاجر اطلاع دهید.
- اجازه دهید مشتری پرداخت کند.
- گواهی امضای تماس گیرنده را تأیید کنید.
برای مشاهده پرداخت های وب در عمل، نسخه ی نمایشی پرداخت وب اندروید را بررسی کنید.
مرحله 1: به تاجران اجازه دهید برنامه پرداخت شما را کشف کنند
طبق دستورالعملهای موجود در تنظیم روش پرداخت، ویژگی related_applications
را در مانیفست برنامه وب تنظیم کنید.
برای اینکه یک تاجر بتواند از برنامه پرداخت شما استفاده کند، باید از API درخواست پرداخت استفاده کند و روش پرداختی را که پشتیبانی میکنید با استفاده از شناسه روش پرداخت مشخص کند.
اگر یک شناسه روش پرداخت دارید که مختص برنامه پرداخت شما است، می توانید مانیفست روش پرداخت خود را تنظیم کنید تا مرورگرها بتوانند برنامه شما را پیدا کنند.
مرحله 2: به تاجر اطلاع دهید که آیا یک مشتری ابزار ثبت نام شده ای دارد که آماده پرداخت است
تاجر می تواند با hasEnrolledInstrument()
تماس بگیرد تا بپرسد آیا مشتری قادر به پرداخت است یا خیر . برای پاسخ به این پرسش میتوانید IS_READY_TO_PAY
بهعنوان یک سرویس Android پیادهسازی کنید.
AndroidManifest.xml
خدمات خود را با یک فیلتر قصد با عملکرد org.chromium.intent.action.IS_READY_TO_PAY
اعلان کنید.
<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
اختیاری است. اگر چنین کنترلکنندهای در برنامه پرداخت وجود نداشته باشد، مرورگر وب فرض میکند که برنامه همیشه میتواند پرداختها را انجام دهد.
ایدل
API برای سرویس IS_READY_TO_PAY
در AIDL تعریف شده است. دو فایل AIDL با محتوای زیر ایجاد کنید:
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
ساده ترین پیاده سازی IsReadyToPayService
در مثال زیر نشان داده شده است:
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
}
}
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;
}
}
پاسخ
این سرویس می تواند پاسخ خود را با استفاده از روش handleIsReadyToPay(Boolean)
ارسال کند.
callback?.handleIsReadyToPay(true)
if (callback != null) {
callback.handleIsReadyToPay(true);
}
اجازه
می توانید از Binder.getCallingUid()
برای بررسی اینکه تماس گیرنده کیست استفاده کنید. توجه داشته باشید که باید این کار را در روش isReadyToPay
انجام دهید، نه در روش onBind
، زیرا سیستم عامل اندروید می تواند اتصال سرویس را کش و مجدداً استفاده کند، که متد onBind()
را راه اندازی نمی کند.
override fun isReadyToPay(callback: IsReadyToPayServiceCallback?, parameters: Bundle?) {
try {
val untrustedPackageName = parameters?.getString("packageName")
val actualPackageNames = packageManager.getPackagesForUid(Binder.getCallingUid())
// ...
@Override
public void isReadyToPay(IsReadyToPayServiceCallback callback, Bundle parameters) {
try {
String untrustedPackageName = parameters != null
? parameters.getString("packageName")
: null;
String[] actualPackageNames = packageManager.getPackagesForUid(Binder.getCallingUid());
// ...
هنگام دریافت تماس های ارتباطات بین فرآیندی (IPC) همیشه پارامترهای ورودی را از null
بررسی کنید. این امر به ویژه مهم است زیرا نسخهها یا فورکهای مختلف سیستمعامل اندروید میتوانند به شیوههای غیرمنتظرهای رفتار کنند و در صورت عدم رسیدگی منجر به خطا شوند.
در حالی که packageManager.getPackagesForUid()
معمولاً یک عنصر را برمی گرداند، کد شما باید سناریوی غیر معمول را که در آن تماس گیرنده از چندین نام بسته استفاده می کند، کنترل کند. این تضمین می کند که برنامه شما قوی باقی می ماند.
به تأیید گواهی امضای تماس گیرنده درباره نحوه تأیید اینکه بسته تماس گیرنده دارای امضای مناسب است مراجعه کنید.
پارامترها
parameters
Bundle در Chrome 139 اضافه شد. همیشه باید با null
بررسی شود.
پارامترهای زیر در parameters
Bundle به سرویس منتقل می شوند:
-
packageName
-
methodNames
-
methodData
-
topLevelOrigin
-
paymentRequestOrigin
-
topLevelCertificateChain
packageName
در Chrome 138 اضافه شده است. قبل از استفاده از مقدار آن، باید این پارامتر را در برابر Binder.getCallingUid()
تأیید کنید. این تأیید ضروری است زیرا بسته parameters
تحت کنترل کامل تماس گیرنده است، در حالی که Binder.getCallingUid()
توسط سیستم عامل Android کنترل می شود.
topLevelCertificateChain
در WebView و وبسایتهای غیرhttps که معمولاً برای آزمایش محلی استفاده میشوند، مانند http://localhost
، null
است.
مرحله 3: اجازه دهید مشتری پرداخت کند
تاجر برای راه اندازی برنامه پرداخت، show()
را فرا می خواند تا مشتری بتواند پرداختی را انجام دهد. برنامه پرداخت با استفاده از Intent PAY
Android با اطلاعات تراکنش در پارامترهای intent فراخوانی می شود.
برنامه پرداخت با methodName
و details
پاسخ میدهد که مختص برنامه پرداخت هستند و برای مرورگر غیرشفاف هستند. مرورگر رشته details
را به یک فرهنگ لغت جاوا اسکریپت برای تاجر با استفاده از deserialization رشته JSON تبدیل می کند، اما هیچ اعتباری فراتر از آن اعمال نمی کند. مرورگر details
تغییر نمی دهد. مقدار آن پارامتر مستقیماً به فروشنده ارسال می شود.
AndroidManifest.xml
فعالیت با فیلتر هدف PAY
باید دارای یک برچسب <meta-data>
باشد که شناسه روش پرداخت پیشفرض برای برنامه را مشخص میکند .
برای پشتیبانی از چندین روش پرداخت، یک تگ <meta-data>
با منبع <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
باید فهرستی از رشته ها باشد که هر کدام باید یک URL معتبر و مطلق با طرح HTTPS باشد که در اینجا نشان داده شده است.
<?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>
پارامترها
پارامترهای زیر به عنوان Intent
اضافی به فعالیت ارسال می شوند:
-
methodNames
-
methodData
-
merchantName
-
topLevelOrigin
-
topLevelCertificateChain
-
paymentRequestOrigin
-
total
-
modifiers
-
paymentRequestId
-
paymentOptions
shippingOptions
val extras: Bundle? = getIntent()?.extras
Bundle extras = getIntent() != null ? getIntent().getExtras() : null;
روش نام ها
نام روش های مورد استفاده عناصر کلیدهای فرهنگ لغت methodData
هستند. اینها روش هایی هستند که برنامه پرداخت پشتیبانی می کند.
val methodNames: List<String>? = extras.getStringArrayList("methodNames")
List<String> methodNames = extras.getStringArrayList("methodNames");
methodData
نگاشت هر یک از methodNames
به methodData
.
val methodData: Bundle? = extras.getBundle("methodData")
Bundle methodData = extras.getBundle("methodData");
merchantName
محتویات تگ HTML <title>
صفحه تسویه حساب تاجر (زمینه مرور سطح بالای مرورگر).
val merchantName: String? = extras.getString("merchantName")
String merchantName = extras.getString("merchantName");
topLevelOrigin
مبدا تاجر بدون طرح (The scheme-less origin of the level browsing context). به عنوان مثال، https://mystore.com/checkout
به عنوان mystore.com
منتقل می شود.
val topLevelOrigin: String? = extras.getString("topLevelOrigin")
String topLevelOrigin = extras.getString("topLevelOrigin");
topLevelCertificateChain
زنجیره گواهی تاجر (زنجیره گواهی زمینه مرور سطح بالا). مقدار برای WebView، localhost یا یک فایل روی دیسک null
است. هر Parcelable
یک Bundle با یک کلید certificate
و یک مقدار آرایه بایت است.
val topLevelCertificateChain: Array<Parcelable>? =
extras.getParcelableArray("topLevelCertificateChain")
val list: List<ByteArray>? = topLevelCertificateChain?.mapNotNull { p ->
(p as Bundle).getByteArray("certificate")
}
Parcelable[] topLevelCertificateChain =
extras.getParcelableArray("topLevelCertificateChain");
if (topLevelCertificateChain != null) {
for (Parcelable p : topLevelCertificateChain) {
if (p != null && p instanceof Bundle) {
((Bundle) p).getByteArray("certificate");
}
}
}
paymentRequestOrigin
مبدأ بدون طرح زمینه مرور iframe که سازنده new PaymentRequest(methodData, details, options)
در جاوا اسکریپت فراخوانی میکند. اگر سازنده از زمینه سطح بالا فراخوانی شده باشد، آنگاه مقدار این پارامتر با مقدار پارامتر topLevelOrigin
برابر است.
val paymentRequestOrigin: String? = extras.getString("paymentRequestOrigin")
String paymentRequestOrigin = extras.getString("paymentRequestOrigin");
total
رشته JSON نشان دهنده کل مبلغ تراکنش است.
val total: String? = extras.getString("total")
String total = extras.getString("total");
در اینجا یک نمونه از محتوای رشته آمده است:
{"currency":"USD","value":"25.00"}
modifiers
خروجی JSON.stringify(details.modifiers)
، که در آن details.modifiers
فقط شامل supportedMethods
، data
و total
است.
paymentRequestId
فیلد PaymentRequest.id
که برنامههای «Push-payment» باید با وضعیت تراکنش مرتبط باشند. وبسایتهای بازرگان از این فیلد برای پرس و جو از برنامههای «پرداخت فشاری» برای وضعیت تراکنش خارج از باند استفاده میکنند.
val paymentRequestId: String? = extras.getString("paymentRequestId")
String paymentRequestId = extras.getString("paymentRequestId");
پاسخ
فعالیت می تواند پاسخ خود را از طریق setResult
با RESULT_OK
ارسال کند.
setResult(Activity.RESULT_OK, Intent().apply {
putExtra("methodName", "https://bobbucks.dev/pay")
putExtra("details", "{\"token\": \"put-some-data-here\"}")
})
finish()
Intent result = new Intent();
Bundle extras = new Bundle();
extras.putString("methodName", "https://bobbucks.dev/pay");
extras.putString("instrumentDetails", "{\"token\": \"put-some-data-here\"}");
result.putExtras(extras);
setResult(Activity.RESULT_OK, result);
finish();
شما باید دو پارامتر را به عنوان Intent اضافه کنید:
-
methodName
: نام روش مورد استفاده. -
details
: رشته JSON حاوی اطلاعات لازم برای تاجر برای تکمیل تراکنش است. اگر موفقیتtrue
باشد،details
باید به گونه ای ساخته شوند کهJSON.parse(details)
موفق شود. اگر هیچ داده ای وجود ندارد که باید برگردانده شود، این رشته می تواند"{}"
باشد، که وب سایت تاجر آن را به عنوان یک فرهنگ لغت جاوا اسکریپت خالی دریافت می کند.
اگر تراکنش در برنامه پرداخت انجام نشده باشد، میتوانید RESULT_CANCELED
پاس کنید، برای مثال، اگر کاربر موفق به تایپ کد پین صحیح حساب خود در برنامه پرداخت نشده باشد. ممکن است مرورگر به کاربر اجازه دهد برنامه پرداخت دیگری را انتخاب کند.
setResult(Activity.RESULT_CANCELED)
finish()
setResult(Activity.RESULT_CANCELED);
finish();
اگر نتیجه فعالیت یک پاسخ پرداخت دریافتی از برنامه پرداخت فراخوانی شده روی RESULT_OK
تنظیم شود، Chrome methodName
خالی و details
در موارد اضافی آن بررسی میکند. اگر اعتبار سنجی ناموفق باشد، Chrome یک وعده رد شده از request.show()
با یکی از برنامهنویسهای زیر با پیامهای خطا برمیگرداند:
'Payment app returned invalid response. Missing field "details".'
'Payment app returned invalid response. Missing field "methodName".'
اجازه
فعالیت می تواند تماس گیرنده را با متد getCallingPackage()
خود بررسی کند.
val caller: String? = callingPackage
String caller = getCallingPackage();
مرحله آخر تأیید گواهی امضای تماس گیرنده است تا تأیید شود بسته تماس گیرنده دارای امضای مناسب است.
مرحله 4: گواهی امضای تماس گیرنده را تأیید کنید
می توانید نام بسته تماس گیرنده را با Binder.getCallingUid()
در IS_READY_TO_PAY
و با Activity.getCallingPackage()
در PAY
بررسی کنید. برای اینکه واقعاً تأیید کنید که تماسگیرنده مرورگری است که در ذهن دارید، باید گواهی امضای آن را بررسی کنید و مطمئن شوید که با مقدار صحیح مطابقت دارد.
اگر سطح API 28 و بالاتر را هدف قرار میدهید و با مرورگری که دارای گواهی امضای واحد است یکپارچه میشوید، میتوانید از PackageManager.hasSigningCertificate()
استفاده کنید.
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
)
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()
برای مرورگرهای گواهی واحد ترجیح داده می شود، زیرا چرخش گواهی را به درستی مدیریت می کند. (Chrome یک گواهی امضا دارد.) برنامه هایی که چندین گواهی امضا دارند نمی توانند آنها را بچرخانند.
اگر نیاز به پشتیبانی از سطوح API 27 و بالاتر دارید، یا اگر نیاز به مدیریت مرورگرهایی با چندین گواهی امضا دارید، میتوانید از PackageManager.GET_SIGNATURES
استفاده کنید.
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)
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);
اشکال زدایی
برای مشاهده خطاها یا پیام های اطلاعاتی از دستور زیر استفاده کنید:
adb logcat | grep -i pay