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

بیاموزید که چگونه برنامه پرداخت اندروید خود را برای کار با پرداخت‌های وب تطبیق دهید و تجربه کاربری بهتری را برای مشتریان فراهم کنید.

منتشر شده: ۵ مه ۲۰۲۰، آخرین به‌روزرسانی: ۲۷ مه ۲۰۲۵

API درخواست پرداخت، یک رابط کاربری مبتنی بر مرورگر داخلی را به وب می‌آورد که به کاربران امکان می‌دهد اطلاعات پرداخت مورد نیاز را آسان‌تر از همیشه وارد کنند. این API همچنین می‌تواند برنامه‌های پرداخت مخصوص پلتفرم را فراخوانی کند.

Browser Support

  • کروم: ۶۰.
  • لبه: ۱۵.
  • فایرفاکس: پشت یک پرچم.
  • سافاری: ۱۱.۱.

Source

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

در مقایسه با استفاده صرف از Android Intents، پرداخت‌های وب امکان ادغام بهتر با مرورگر، امنیت و تجربه کاربری را فراهم می‌کنند:

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

برای پیاده‌سازی پرداخت‌های وب در یک برنامه پرداخت اندروید، چهار مرحله لازم است:

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

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

مرحله ۱: اجازه دهید فروشندگان، اپلیکیشن پرداخت شما را کشف کنند

ویژگی related_applications را در مانیفست برنامه وب طبق دستورالعمل‌های موجود در بخش «تنظیم روش پرداخت» تنظیم کنید.

برای اینکه یک تاجر بتواند از برنامه پرداخت شما استفاده کند، باید از API درخواست پرداخت استفاده کند و روش پرداختی را که شما پشتیبانی می‌کنید با استفاده از شناسه روش پرداخت مشخص کند.

اگر شناسه روش پرداختی دارید که مختص برنامه پرداخت شما است، می‌توانید مانیفست روش پرداخت خود را تنظیم کنید تا مرورگرها بتوانند برنامه شما را کشف کنند.

مرحله ۲: اگر مشتری ابزار ثبت‌شده‌ای دارد که آماده پرداخت است، به فروشنده اطلاع دهید.

فروشنده می‌تواند تابع hasEnrolledInstrument() را فراخوانی کند تا بررسی کند که آیا مشتری قادر به پرداخت است یا خیر . شما می‌توانید IS_READY_TO_PAY به عنوان یک سرویس اندروید برای پاسخ به این پرسش پیاده‌سازی کنید.

AndroidManifest.xml

سرویس خود را با یک فیلتر intent با اکشن 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 اختیاری است. اگر چنین کنترل‌کننده‌ی intent در برنامه‌ی پرداخت وجود نداشته باشد، مرورگر وب فرض می‌کند که برنامه همیشه می‌تواند پرداخت‌ها را انجام دهد.

آیدل

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 در کروم ۱۳۹ اضافه شده‌اند. همیشه باید در برابر null بررسی شوند.

پارامترهای زیر در قسمت parameters به سرویس ارسال می‌شوند:

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

packageName در کروم ۱۳۸ اضافه شده است. قبل از استفاده از مقدار این پارامتر، باید آن را با Binder.getCallingUid() مقایسه کنید. این بررسی ضروری است زیرا مجموعه parameters تحت کنترل کامل فراخواننده است، در حالی که Binder.getCallingUid() توسط سیستم عامل اندروید کنترل می‌شود.

topLevelCertificateChain در WebView و در وب‌سایت‌های غیر https که معمولاً برای آزمایش محلی استفاده می‌شوند، مانند http://localhost null است.

مرحله ۳: اجازه دهید مشتری پرداخت را انجام دهد

فروشنده تابع show() را برای اجرای برنامه پرداخت فراخوانی می‌کند تا مشتری بتواند پرداخت انجام دهد. برنامه پرداخت با استفاده از یک اینتنت اندروید به نام PAY که اطلاعات تراکنش در پارامترهای اینتنت قرار دارد، فراخوانی می‌شود.

برنامه پرداخت با 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 extras به activity ارسال می‌شوند:

  • 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

مبدا فروشنده بدون طرح (مبدأ بدون طرح زمینه مرور سطح بالا). برای مثال، 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 که برنامه‌های "پرداخت از طریق فشار" باید آن را به وضعیت تراکنش مرتبط کنند. وب‌سایت‌های فروشنده از این فیلد برای استعلام وضعیت تراکنش خارج از محدوده از برنامه‌های "پرداخت از طریق فشار" استفاده می‌کنند.

کاتلین

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("details", "{\"token\": \"put-some-data-here\"}");
result.putExtras(extras);
setResult(Activity.RESULT_OK, result);
finish();

شما باید دو پارامتر را به عنوان Intent extras مشخص کنید:

  • methodName : نام متدی که استفاده می‌شود.
  • details : رشته JSON حاوی اطلاعات لازم برای تکمیل تراکنش توسط فروشنده. اگر success برابر با true باشد، details باید به گونه‌ای ساخته شود که JSON.parse(details) با موفقیت انجام شود. اگر داده‌ای برای برگرداندن وجود نداشته باشد، این رشته می‌تواند "{}" باشد که وب‌سایت فروشنده آن را به عنوان یک دیکشنری جاوا اسکریپت خالی دریافت خواهد کرد.

اگر کاربر تراکنش را در برنامه پرداخت لغو کند، می‌توانید RESULT_CANCELED ارسال کنید. انجام این کار باعث می‌شود request.show() با خطای AbortError در وب‌سایت فروشنده رد شود که نشان‌دهنده انصراف کاربر است.

کاتلین

setResult(Activity.RESULT_CANCELED)
finish()

جاوا

setResult(Activity.RESULT_CANCELED);
finish();

از کروم ۱۴۹، مقادیر نتیجه زیر پشتیبانی می‌شوند:

کاتلین

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

جاوا

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

اگر برنامه پرداخت به دلیل خطای داخلی با شکست مواجه شود، می‌توانید با ارسال Activity.RESULT_FIRST_USER به عنوان کد نتیجه، آن را نشان دهید.

اگر INTERNAL_PAYMENT_APP_ERROR برگردانده شود، request.show() با OperationError در وب‌سایت فروشنده رد می‌شود که نشان‌دهنده‌ی خطایی در برنامه‌ی پرداخت است.

این تمایز بین RESULT_CANCELED (0) برای لغو کاربر، که باعث AbortError می‌شود، و INTERNAL_PAYMENT_APP_ERROR (1) برای خطای داخلی برنامه، که باعث OperationError می‌شود، به فروشندگان اجازه می‌دهد تا جریان‌های کاربری بهتری ایجاد کنند.

کاتلین

setResult(Activity.RESULT_FIRST_USER)
finish()

جاوا

setResult(Activity.RESULT_FIRST_USER);
finish();

اگر نتیجه فعالیت پاسخ پرداخت دریافتی از برنامه پرداخت فراخوانی شده روی RESULT_OK تنظیم شده باشد، کروم در موارد اضافی خود methodName و details غیر خالی را بررسی می‌کند. اگر اعتبارسنجی با شکست مواجه شود، کروم یک promise رد شده از 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();

مرحله آخر، تأیید گواهی امضای تماس‌گیرنده است تا تأیید شود که بسته فراخوانی، امضای صحیح را دارد.

مرحله ۴: گواهی امضای تماس‌گیرنده را تأیید کنید

می‌توانید نام بسته‌ی فراخواننده را با استفاده از Binder.getCallingUid() در IS_READY_TO_PAY و با Activity.getCallingPackage() در PAY بررسی کنید. برای اینکه واقعاً تأیید کنید که فراخواننده همان مرورگری است که در نظر دارید، باید گواهی امضای آن را بررسی کنید و مطمئن شوید که با مقدار صحیح مطابقت دارد.

اگر هدف شما API سطح ۲۸ و بالاتر است و می‌خواهید آن را با مرورگری که دارای گواهی امضای یکپارچه است ادغام کنید، می‌توانید 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() برای مرورگرهای تک گواهی ترجیح داده می‌شود، زیرا به درستی چرخش گواهی را مدیریت می‌کند. (کروم دارای یک گواهی امضای واحد است.) برنامه‌هایی که چندین گواهی امضای مختلف دارند، نمی‌توانند آنها را بچرخانند.

اگر نیاز به پشتیبانی از API سطح ۲۷ و قبل از آن دارید، یا اگر نیاز به مدیریت مرورگرهایی با چندین گواهی امضا دارید، می‌توانید 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