راهنمای پرداخت درونبرنامهای مایکت در React Native
برای مشاهده نمونه کامل پیادهسازی پرداخت درونبرنامهای مایکت در React Native و Expo، به پروژه نمونه رسمی در GitHub مراجعه کنید.
کتابخانه iab-myket-reactnative ابزار ر سمی برای پیادهسازی پرداخت درونبرنامهای مایکت در برنامههای React Native و Expo است. این کتابخانه از کامپوننتهای تابعی (با هوک useMyket) و کامپوننتهای مبتنی بر کلاس (با شیء myket) پشتیبانی میکند.
نکته: این کتابخانه فقط از سیستمعامل اندروید پشتیبانی میکند. در پروژههای Expo تهیه خروجی محلی بومی (Native Prebuild) الزامی است.
قابلیتها
- رابط مبتنی بر Promise: پشتیبانی از ساختار
async/await. - هوک React: ارائه هوک
useMyketبرای مدیریت چرخه اتصال در کامپوننتهای تابعی. - پشتیبانی از Bare React Native و Expo: سازگار با پروژههای Bare و Managed Expo (از طریق Config Plugin).
- پشتیبانی از انواع محصولات: مدیریت خریدهای یکباره شامل محصولات مصرفشدنی (سکه، اعتبار) و محصولات ماندگار و غیرمصرفی (ارتقا به نسخه ویژه، حذف تبلیغات).
- استعلام موجودی و بازیابی خریدها: استعلام خریدهای فعال کاربر و بازیابی سفارشهای مصرفنشده پس از قطعی ارتباط.
- پشتیبانی از تایپهای TypeScript: همراه با تعاریف تایپ TypeScript.
- لاگهای دیباگ بومی: امکان فعالسازی لاگهای Logcat با متد
enableDebugging().
پیشنیازها
پیش از شروع پیادهسازی، موارد زیر را آماده کنید:
۱. حساب کاربری در پنل توسعهدهندگان مایکت.
۲. ثبت برنامه در پنل مایکت و رزرو شناسه بسته (applicationId).
۳. تعریف محصولات درونبرنامهای (SKU) در پنل برنامه.
۴. دریافت کلید عمومی RSA پرداخت برنامه از پنل توسعهدهندگان مایکت.
۵. نصب آخرین نسخه برنامه مایکت روی دستگاه تست فیزیکی یا شبیهساز اندروید.
مقدار applicationId در تنظیمات اندروید پروژه شما باید دقیقاً با شناسه بسته ث بتشده در پنل مایکت یکسان باشد.
نصب کتابخانه
کتابخانه را با استفاده از ابزار مدیریت بسته دلخواه خود نصب کنید:
# با npm
npm install iab-myket-reactnative
# یا با yarn
yarn add iab-myket-reactnative
پس از نصب کتابخانه، برنامه اندرویدی حتماً باید مجدداً کامپایل و بیلد شود (npx react-native run-android یا npx expo run:android). اجرای مجدد سرور Metro بهتنهایی برای بارگذاری ماژولهای بومی اندروید کافی نیست.
پیکربندی اندروید (Android Configuration)
سرویس پرداخت مایکت از مشخصههای جایگزین در مانیفست (manifestPlaceholders) برای اتصال پویا به سرویس بازار استفاده میکند.
حالت اول: پروژههای استاندارد React Native (Bare)
در فایل android/app/build.gradle (Groovy DSL)
تنظیمات زیر را در بلوک android { defaultConfig { ... } } قرار دهید:
android {
...
defaultConfig {
...
def marketApplicationId = "ir.mservices.market"
def marketBindAddress = "ir.mservices.market.InAppBillingService.BIND"
manifestPlaceholders = [
marketApplicationId: "${marketApplicationId}",
marketBindAddress : "${marketBindAddress}",
marketPermission : "${marketApplicationId}.BILLING"
]
}
}
در فایل android/app/build.gradle.kts (Kotlin DSL)
android {
...
defaultConfig {
...
manifestPlaceholders["marketApplicationId"] = "ir.mservices.market"
manifestPlaceholders["marketBindAddress"] = "ir.mservices.market.InAppBillingService.BIND"
manifestPlaceholders["marketPermission"] = "ir.mservices.market.BILLING"
}
}
حالت دوم: پروژههای Expo (Managed / EAS Build)
در پروژههای Expo که از Continuous Native Generation (CNG) یا دستور npx expo prebuild استفاده میکنند، از یک Config Plugin برای تزریق مشخصهها به build.gradle استفاده میشود:
۱. ایجاد فایل plugins/withMarketPlace.js
یک پوشه به نام plugins در ریشه پروژه ایجاد کرده و فایل withMarketPlace.js را در آن بسازید:
const { withAppBuildGradle } = require('@expo/config-plugins');
const withMarketPlace = (config, options = {}) => {
const { marketApplicationId, marketBindAddress, marketPermission } = options;
return withAppBuildGradle(config, (modConfig) => {
if (modConfig.modResults.language === 'groovy') {
const placeholders = `
manifestPlaceholders = [
marketApplicationId: "${marketApplicationId}",
marketBindAddress: "${marketBindAddress}",
marketPermission: "${marketPermission}"
]
`;
if (!modConfig.modResults.contents.includes('marketApplicationId')) {
modConfig.modResults.contents = modConfig.modResults.contents.replace(
/defaultConfig\s*\{/,
(match) => `${match}\n${placeholders}`
);
}
}
return modConfig;
});
};
module.exports = withMarketPlace;
۲. ثبت پلاگین در app.config.js
const withMarketPlace = require('./plugins/withMarketPlace');
export default ({ config }) => ({
...config,
plugins: [
...(config.plugins || []),
[
withMarketPlace,
{
marketApplicationId: "ir.mservices.market",
marketBindAddress: "ir.mservices.market.InAppBillingService.BIND",
marketPermission: "ir.mservices.market.BILLING"
}
]
]
});
پس از اعمال تنظیمات، خروجی اندروید را تولید و اجرا کنید:
npx expo run:android
راهنمای پیادهسازی و نمونهکدها
۱. پیادهسازی در کامپوننتهای تابعی با هوک useMyket (توصیهشده)
هوک useMyket اتصال خودکار به سرویس در زمان Mount شدن کامپوننت و قطع اتصال هنگام Unmount را مدیریت میکند:
import React, { useState } from 'react';
import { View, Button, Text, Alert } from 'react-native';
import { useMyket } from 'iab-myket-reactnative';
const RSA_PUBLIC_KEY = "YOUR_BASE64_RSA_PUBLIC_KEY_FROM_MYKET";
export default function ShopScreen() {
const myket = useMyket(RSA_PUBLIC_KEY);
const [loading, setLoading] = useState(false);
// استعلام محصولات و خریدهای مصرفنشده قبلی
const loadStoreItems = async () => {
try {
setLoading(true);
const inventory = await myket.queryPurchaseProduct(true, ['coin_pack_100', 'remove_ads']);
console.log('Available Products:', inventory.allProducts);
// مصرف خریدهای مصرفی پردازشنشده قبلی (بازیابی خرید)
for (const purchase of inventory.allPurchases) {
if (purchase.sku === 'coin_pack_100') {
await myket.consumePurchase(purchase);
console.log('Consumed pending purchase:', purchase.orderId);
}
}
} catch (error) {
console.error('Failed to load products:', error);
} finally {
setLoading(false);
}
};
// شروع فرآیند خرید
const handleBuy = async (sku: string) => {
try {
const developerPayload = `payload_${Date.now()}`;
const purchase = await myket.purchaseProduct(sku, developerPayload);
// اعتبارسنجی payload
if (purchase.developerPayload === developerPayload) {
// اگر محصول از نوع مصرفشدنی باشد:
if (purchase.sku === 'coin_pack_100') {
await myket.consumePurchase(purchase);
Alert.alert('موفقیت', 'سکه با موفقیت خریداری و مصرف شد!');
} else {
// محصول غیرمصرفی (مانند حذف تبلیغات)
Alert.alert('موفقیت', 'ارتقا به نسخه ویژه با موفقیت انجام شد!');
}
}
} catch (error) {
Alert.alert('خطا در خرید', JSON.stringify(error));
}
};
return (
<View style={{ flex: 1, justifyContent: 'center', padding: 16 }}>
<Button title="دریافت موجودی و بازیابی" onPress={loadStoreItems} disabled={loading} />
<Button title="خرید بسته ۱۰۰ سکه" onPress={() => handleBuy('coin_pack_100')} />
</View>
);
}
۲. پیادهسازی در کامپوننتهای کلاسی (شیء myket)
در کامپوننتهای کلاسی، چرخه حیات اتصال را با متدهای connect و disconnect مدیریت کنید:
import React, { Component } from 'react';
import { View, Button, Alert } from 'react-native';
import myket, { Inventory, Purchase } from 'iab-myket-reactnative';
const RSA_PUBLIC_KEY = "YOUR_BASE64_RSA_PUBLIC_KEY_FROM_MYKET";
class ShopScreen extends Component {
async componentDidMount() {
try {
// فعالسازی لاگها در محیط توسعه
myket.enableDebugging(__DEV__);
// اتصال به سرویس پرداخت مایکت
await myket.connect(RSA_PUBLIC_KEY);
this.fetchInventory();
} catch (error) {
console.error('Connection failed:', error);
}
}
componentWillUnmount() {
// آزادسازی منابع هنگام خروج از صفحه
myket.disconnect().catch(console.error);
}
fetchInventory = async () => {
try {
const inventory: Inventory = await myket.queryPurchaseProduct(true, ['premium_upgrade']);
console.log('Products:', inventory.allProducts);
} catch (error) {
console.error('Query failed:', error);
}
};
buyProduct = async (sku: string) => {
try {
const payload = 'my_unique_tracking_payload';
const purchase: Purchase = await myket.purchaseProduct(sku, payload);
Alert.alert('خرید موفق', `شناسه سفارش: ${purchase.orderId}`);
} catch (error) {
Alert.alert('خطا در خرید', JSON.stringify(error));
}
};
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', padding: 16 }}>
<Button title="ارتقا به نسخه ویژه" onPress={() => this.buyProduct('premium_upgrade')} />
</View>
);
}
}
export default ShopScreen;
مرجع API
myket.connect(rsaKey: string): Promise<string>
سرویس پرداخت مایکت را مقداردهی کرده و اتصال را برقرار میکند.
rsaKey: کلید عمومی RSA برنامه دریافت شده از پنل توسعهدهندگان مایکت.- خروجی: یک
Promiseکه در صورت موفقیت با پیام متنی Resolve میشود یا در صورت بروز خطا (مانند عدم نصب مایکت) Reject میگردد.
myket.disconnect(): Promise<void>
اتصال به سرویس پرداخت را قطع کرده و منابع بومی اندروید را آزاد میکند.
myket.enableDebugging(enable: boolean): void
نمایش لاگهای دیباگ پرداخت را در Logcat اندروید فعال یا غیرفعال میکند.
myket.queryPurchaseProduct(querySkuDetails: boolean, moreSkus?: string[]): Promise<Inventory>
اطلاعات محصولات و خریدهای فعال کاربر را استعلام میکند.
querySkuDetails: مقدارtrueبرای دریافت مشخصات محصول (قیمت، عنوان، توضیحات)، وfalseبرای دریافت صرف وضعیت خریدها.moreSkus: آرایهای از شناسههای SKU محصولات جهت استعلام قیمت و مشخصات.- خروجی: یک شیء
Inventoryشامل آرایههایallProductsوallPurchases.
همیشه هنگام شروع برنامه یا ورود به بخش فروشگاه، متد queryPurchaseProduct را فراخوانی کنید تا خریدهایی که هزینه آنها کسر شده اما قبل از مصرف یا تحویل، برنامه بسته شده است، بازیابی و مصرف شوند.
myket.purchaseProduct(sku: string, developerPayload?: string): Promise<Purchase>
دیالوگ پرداخت مایکت را برای یک محصول مشخص نمایش میدهد.
sku: شناسه یکتای محصول ثبتشده در پنل مایکت.developerPayload: رشته اختیاری جهت اتصال به تراکنش برای اعتبارسنجی و جلوگیری از حملات Replay.- خروجی: در صورت پرداخت موفق، یک شیء
Purchaseبرمیگرداند.
myket.consumePurchase(purchase: Purchase): Promise<void>
محصول خریداریشده از نوع مصرفشدنی را مصرف میکند تا کاربر بتواند در آینده مجدداً آن را خریداری نماید.
purchase: شیءPurchaseحاصل از خرید یا استعلام موجودی.
محصولات غیرمصرفی (Non-consumable مانند حذف تبلیغات یا باز کردن مراحل) را هرگز مصرف نکنید. مصرف کالا آن را از لیست خریدهای فعال کاربر در مایکت حذف میکند.
useMyket(rsaKey: string): typeof myket
هوک اختصاصی React که با Mount شدن کامپوننت متصل شده و با Unmount شدن، اتصال را قطع میکند.