راهنمای توسعه دهندگان اپلیکیشن پرداخت اندروید

بیاموزید که چگونه برنامه پرداخت Android خود را با Web Payments سازگار کنید و تجربه کاربری بهتری برای مشتریان فراهم کنید.

تاریخ انتشار: 5 می 2020، آخرین به روز رسانی: 27 می 2025

Payment Request API یک رابط داخلی مبتنی بر مرورگر را به وب می‌آورد که به کاربران اجازه می‌دهد اطلاعات پرداخت مورد نیاز را راحت‌تر از همیشه وارد کنند. API همچنین می تواند برنامه های پرداخت ویژه پلتفرم را فراخوانی کند.

Browser Support

  • کروم: 60.
  • لبه: 15.
  • فایرفاکس: پشت پرچم
  • سافاری: 11.1.

Source

جریان پرداخت با برنامه Google Pay مخصوص پلتفرم که از پرداخت‌های وب استفاده می‌کند.

در مقایسه با استفاده از Intent Android، Web Payments امکان ادغام بهتر با مرورگر، امنیت و تجربه کاربر را فراهم می کند:

  • برنامه پرداخت به صورت مودال در زمینه وب سایت تاجر راه اندازی شده است.
  • پیاده سازی مکمل برنامه پرداخت موجود شما است و به شما امکان می دهد از پایگاه کاربری خود استفاده کنید.
  • برای جلوگیری از بارگذاری جانبی، امضای برنامه پرداخت بررسی می‌شود.
  • برنامه های پرداخت می توانند چندین روش پرداخت را پشتیبانی کنند.
  • هر روش پرداخت مانند ارز دیجیتال، انتقال بانکی و غیره را می توان یکپارچه کرد. برنامه‌های پرداخت در دستگاه‌های اندرویدی حتی می‌توانند روش‌هایی را که نیاز به دسترسی به تراشه سخت‌افزاری روی دستگاه دارند، ادغام کنند.

برای پیاده سازی Web Payments در یک برنامه پرداخت Android، چهار مرحله لازم است:

  1. به تاجران اجازه دهید برنامه پرداخت شما را کشف کنند.
  2. اگر مشتری ابزار ثبت نامی (مانند کارت اعتباری) آماده پرداخت را دارد، به تاجر اطلاع دهید.
  3. اجازه دهید مشتری پرداخت کند.
  4. گواهی امضای تماس گیرنده را تأیید کنید.

برای مشاهده پرداخت های وب در عمل، نسخه ی نمایشی پرداخت وب اندروید را بررسی کنید.

مرحله 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