راهنمای پرداخت درونبرنامهای مایکت در Java و Kotlin
برای مشاهده نمونه کامل پرداخت درونبرنامهای مایکت در اندروید، به پروژه نمونه در GitHub مراجعه کنید.
کتابخانه Myket Billing Client SDK ابزار رسمی اندروید برای پیادهس ازی پرداخت درونبرنامهای (IAP) در برنامههای توزیعشده از طریق فروشگاه مایکت است و از پروژههای Kotlin و Java پشتیبانی میکند.
قابلیتها
- محصولات درونبرنامهای: پشتیبانی از محصولات مصرفشدنی (مانند سکه، بنزین، اعتبار) و محصولات غیرمصرفی (مانند نسخه ویژه و حذف تبلیغات).
- اعتبارسنجی خودکار امضا: بررسی صحت امضای دیجیتال با کلید عمومی RSA.
- مکانیزم ارتباط سهسطحی:
۱. اتصال IPC / AIDL: اتصال مستقیم به سرویس پرداخت کلاینت مایکت.
۲. کانال جایگزین Broadcast Receiver: در شرایط محدودیت اتصال سرویس.
۳. پرداخت تحت وب (Chrome Custom Tabs): هدایت به درگاه پرداخت وب مایکت در صورت عدم نصب برنامه مایکت روی دستگاه. - پشتیبانی از چند فروشگاه (Multi-Store): ایجاد خروجی برای مارکتهای مختلف با Product Flavors بدون تغییر کد پرداخت.
- مدیریت داخلی نتایج: مدیریت داخلی نتیجه اکتیویتی در نسخه ۲.۲ بدون نیاز به پیادهسازی دستی
onActivityResult. - سازگاری با اندروید ۱۱ و بالاتر: ادغام خودکار اعلانهای دسترسی بسته (
<queries>) در مانیفست.
پیشنیازها
- حداقل نسخه اندروید (Min SDK): اندروید ۵.۰ (API Level 21)
- نسخه کامپایل (Compile SDK): API Level 34 یا بالاتر
- جاوا / کاتلین: Java 11 یا بالاتر (پیشنهادی: Java 17)
مراحل نصب و پیکربندی
۱. افزودن مخزن Maven رسمی مایکت
مخزن رسمی Maven مایکت را در بخش dependencyResolutionManagement در فایل settings.gradle.kts (یا settings.gradle) اضافه کنید:
- Kotlin DSL
- Groovy
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven("https://maven.myket.ir")
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url "https://maven.myket.ir" }
}
}
۲. افزودن وابستگی کتابخانه
میتوانید وابستگی کتابخانه را از طریق Version Catalog (روش توصیهشده) یا مستقیماً در فایل build.gradle ماژول برنامه اضافه کنید.
الف) استفاده از Version Catalog (gradle/libs.versions.toml) (پیشنهادی)
در فایل gradle/libs.versions.toml:
[versions]
myketBilling = "2.2"
[libraries]
myket-billing = { module = "ir.myket:billing", version.ref = "myketBilling" }
سپس در فایل build.gradle.kts ماژول برنامه:
dependencies {
implementation(libs.myket.billing)
}
ب) افزودن مستقیم وابستگی
- Kotlin DSL
- Groovy
dependencies {
implementation("ir.myket:billing:2.2")
}
dependencies {
implementation 'ir.myket:billing:2.2'
}
۳. تنظیم شناسه سرویس و کلید عمومی (Manifest Placeholders)
کتابخانه پرداخت مایکت از مشخصههای جایگزین در مانیفست (manifestPlaceholders) برای اتصال پویا به سرویس پرداخت و درخواست مجوزها استفاده میکند.
حالت پیشفرض (تکفروشگاهی - مایکت)
تنظیمات زیر را در بخش defaultConfig در فایل build.gradle.kts (یا build.gradle) ماژول برنامه قرار دهید:
- Kotlin DSL
- Groovy
android {
defaultConfig {
// ...
val marketApplicationId = "ir.mservices.market"
val marketBindAddress = "ir.mservices.market.InAppBillingService.BIND"
manifestPlaceholders["marketApplicationId"] = marketApplicationId
manifestPlaceholders["marketBindAddress"] = marketBindAddress
manifestPlaceholders["marketPermission"] = "$marketApplicationId.BILLING"
// کلید عمومی Base64 دریافت شده از پنل توسعهدهندگان مایکت
buildConfigField(
"String",
"IAB_PUBLIC_KEY",
"\"YOUR_BASE64_ENCODED_PUBLIC_KEY\""
)
}
}
android {
defaultConfig {
// ...
def marketApplicationId = "ir.mservices.market"
def marketBindAddress = "ir.mservices.market.InAppBillingService.BIND"
manifestPlaceholders = [
marketApplicationId: marketApplicationId,
marketBindAddress: marketBindAddress,
marketPermission: "${marketApplicationId}.BILLING"
]
buildConfigField "String", "IAB_PUBLIC_KEY", "\"YOUR_BASE64_ENCODED_PUBLIC_KEY\""
}
}
مقدار YOUR_BASE64_ENCODED_PUBLIC_KEY را با کلید عمومی برنامه خود که از پنل توسعهدهندگان مایکت دریافت کردهاید جایگزین کنید.
۴. معماری چندفروشگاهی با Product Flavors
اگر قصد دارید برنامه خود را علاوه بر مایکت در فروشگاههای دیگر منتشر کنید، از productFlavors استفاده کنید:
- Kotlin DSL
- Groovy
android {
flavorDimensions += "store"
productFlavors {
create("myket") {
dimension = "store"
val marketApplicationId = "ir.mservices.market"
val marketBindAddress = "ir.mservices.market.InAppBillingService.BIND"
manifestPlaceholders["marketApplicationId"] = marketApplicationId
manifestPlaceholders["marketBindAddress"] = marketBindAddress
manifestPlaceholders["marketPermission"] = "$marketApplicationId.BILLING"
buildConfigField("String", "IAB_PUBLIC_KEY", "\"YOUR_MYKET_PUBLIC_KEY\"")
}
create("bazaar") {
dimension = "store"
val marketApplicationId = "com.farsitel.bazaar"
val marketBindAddress = "ir.cafebazaar.pardakht.InAppBillingService.BIND"
manifestPlaceholders["marketApplicationId"] = marketApplicationId
manifestPlaceholders["marketBindAddress"] = marketBindAddress
manifestPlaceholders["marketPermission"] = "$marketApplicationId.permission.PAY_THROUGH_BAZAAR"
buildConfigField("String", "IAB_PUBLIC_KEY", "\"YOUR_BAZAAR_PUBLIC_KEY\"")
}
}
}
android {
flavorDimensions "store"
productFlavors {
myket {
dimension "store"
def marketApplicationId = "ir.mservices.market"
def marketBindAddress = "ir.mservices.market.InAppBillingService.BIND"
manifestPlaceholders = [
marketApplicationId: marketApplicationId,
marketBindAddress: marketBindAddress,
marketPermission: "${marketApplicationId}.BILLING"
]
buildConfigField "String", "IAB_PUBLIC_KEY", "\"YOUR_MYKET_PUBLIC_KEY\""
}
bazaar {
dimension "store"
def marketApplicationId = "com.farsitel.bazaar"
def marketBindAddress = "ir.cafebazaar.pardakht.InAppBillingService.BIND"
manifestPlaceholders = [
marketApplicationId: marketApplicationId,
marketBindAddress: marketBindAddress,
marketPermission: "${marketApplicationId}.permission.PAY_THROUGH_BAZAAR"
]
buildConfigField "String", "IAB_PUBLIC_KEY", "\"YOUR_BAZAAR_PUBLIC_KEY\""
}
}
}
با این ساختار، منطق کدهای پرداخت کاتلین یا جاوا یکسان میماند و مقادیر متناظر در زمان بیلد بهصورت خودکار جایگزین میشوند.
راهنمای گامبهگام پیادهسازی
مرحله اول: مقداردهی اولیه IabHelper و اتصال (startSetup)
ابتدا در اکتیویتی یا کلاس مدیریت پرداخت، شیء IabHelper را با Context و کلید عمومی مقداردهی کرده و با متد startSetup ارتباط را برقرار کنید:
- Kotlin
- Java
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import ir.myket.billingclient.IabHelper
import ir.myket.billingclient.util.IabResult
import ir.myket.billingclient.util.Inventory
import ir.myket.billingclient.util.Purchase
class MainActivity : AppCompatActivity() {
private var iabHelper: IabHelper? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// مقداردهی اولیه Helper
iabHelper = IabHelper(this, BuildConfig.IAB_PUBLIC_KEY).apply {
enableDebugLogging(BuildConfig.DEBUG, "MyketBilling")
}
// برقراری ارتباط با سرویس پرداخت
iabHelper?.startSetup { result ->
if (!result.isSuccess) {
// بروز خطا در اتصال (مثلاً در دسترس نبودن سرویس)
Log.e("Billing", "Setup failed: ${result.message}")
return@startSetup
}
// ارتباط با موفقیت برقرار شد؛ دریافت موجودی و خریدهای فعال
queryInventory()
}
}
}
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import ir.myket.billingclient.IabHelper;
import ir.myket.billingclient.util.IabResult;
import ir.myket.billingclient.util.Inventory;
import ir.myket.billingclient.util.Purchase;
public class MainActivity extends AppCompatActivity {
private IabHelper mHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHelper = new IabHelper(this, BuildConfig.IAB_PUBLIC_KEY);
mHelper.enableDebugLogging(BuildConfig.DEBUG, "MyketBilling");
mHelper.startSetup(result -> {
if (!result.isSuccess()) {
Log.e("Billing", "Setup failed: " + result.getMessage());
return;
}
// ارتباط موفقیتآمیز است
queryInventory();
});
}
}
پیش از اطمینان از موفقیت startSetup (بررسی result.isSuccess)، نباید هیچکدام از متدهای خرید یا استعلام موجودی را فراخوانی کنید.
مرحله دوم: دریافت موجودی و خریدهای قبلی (queryInventory)
بلافاصله پس از اتصال موفق و در ابتدای اجرای برنامه، باید فهرست محصولات و وضعیت خریدهای فعال کاربر را استعلام بگیرید. این مر حله برای اهداف زیر حیاتی است:
۱. محصولات مصرفشدنی (Consumable): اگر کاربر خریدی انجام داده باشد اما پیش از مصرف و تحویل آن، برنامه بسته شده یا کرش کرده باشد، این خرید در فهرست خریدهای فعال باقی میماند. شما باید این خرید را شناسایی، مصرف و تحویل کاربر دهید.
۲. محصولات غیرمصرفی (Non-consumable): بررسی مالکیت محصولاتی مثل «نسخه حرفهای» و فعالسازی مجدد آن برای کاربر.
۳. اطلاعات محصولات (SKU Details): دریافت عنوان، توضیحات و قیمت لحظهای ثبتشده در پنل توسعهدهندگان مایکت.
- Kotlin
- Java
private fun queryInventory() {
val skuList = listOf("premium_upgrade", "coin_pack_100")
iabHelper?.queryInventoryAsync(true, skuList) { result, inventory ->
if (result.isFailure || inventory == null) {
Log.e("Billing", "Failed to query inventory: ${result.message}")
return@queryInventoryAsync
}
// ۱. بررسی مالکیت محصول غیرمصرفی
val premiumPurchase = inventory.getPurchase("premium_upgrade")
val isPremium = premiumPurchase != null && verifyDeveloperPayload(premiumPurchase)
// ۲. بررسی و مصرف خریدهای مصرفی پردازشنشده قبلی
val coinPurchase = inventory.getPurchase("coin_pack_100")
if (coinPurchase != null && verifyDeveloperPayload(coinPurchase)) {
consumeProduct(coinPurchase)
}
// بهروزرسانی رابط کاربری
updateUi(isPremium)
}
}
private void queryInventory() {
List<String> skuList = Arrays.asList("premium_upgrade", "coin_pack_100");
mHelper.queryInventoryAsync(true, skuList, (result, inventory) -> {
if (result.isFailure() || inventory == null) {
Log.e("Billing", "Failed to query inventory: " + result.getMessage());
return;
}
// بررسی محصول غیرمصرفی
Purchase premiumPurchase = inventory.getPurchase("premium_upgrade");
boolean isPremium = (premiumPurchase != null && verifyDeveloperPayload(premiumPurchase));
// بررسی و مصرف خرید مصرفی معلق
Purchase coinPurchase = inventory.getPurchase("coin_pack_100");
if (coinPurchase != null && verifyDeveloperPayload(coinPurchase)) {
consumeProduct(coinPurchase);
}
updateUi(isPremium);
});
}