Мобильные платежные системы революционизировали способ проведения транзакций на смартфонах и планшетах. От бесконтактных платежей с Apple Pay и Google Pay до встроенных покупок и моделей подписки, тестирование мобильных платежей требует специализированных знаний стандартов безопасности, платформо-специфичных реализаций и требований соответствия. Это всеобъемлющее руководство охватывает все, что нужно знать QA инженерам об эффективном тестировании мобильных платежных систем.
Введение в Задачи Тестирования Мобильных Платежей
Тестирование мобильных платежей представляет уникальные задачи, значительно отличающиеся от традиционного тестирования веб-платежей. Сложность проистекает из множества факторов:
Фрагментация Платформ: Разные операционные системы (iOS, Android) (как обсуждается в Cross-Platform Mobile Testing: Strategies for Multi-Device Success) (как обсуждается в Appium 2.0: New Architecture and Cloud Integration for Modern Mobile Testing) реализуют платежные системы по-разному, требуя платформо-специфичных подходов к тестированию.
Требования Безопасности: Платежные системы должны соответствовать PCI DSS (Стандарт Безопасности Данных Индустрии Платежных Карт), требуя строгого тестирования безопасности и мер защиты данных.
Ограничения Sandbox: Тестовые среды часто имеют ограниченную функциональность по сравнению с продакшеном, делая комплексное тестирование сложным.
Зависимости от Третьих Сторон: Платежные шлюзы, банки и провайдеры платформ вводят внешние зависимости, которые могут влиять на надежность тестирования.
Критичность Пользовательского Опыта: Потоки платежей должны быть плавными и интуитивными, так как трение в процессе платежа напрямую влияет на конверсию и доход.
Ключевые задачи включают:
- Тестирование без раскрытия реальных платежных данных
- Валидация шифрования и безопасной передачи данных
- Симуляция различных сценариев платежей (успех, отказ, таймаут)
- Тестирование на разных устройствах, версиях ОС и условиях сети
- Обеспечение соответствия региональным регуляциям платежей
- Валидация рабочих процессов возврата и отмены
- Тестирование логики подписок и повторяющихся платежей
Тестирование и Интеграция Apple Pay
Интеграция Apple Pay требует специфических стратегий тестирования из-за ограничений экосистемы и модели безопасности.
Настройка Тестовой Среды Apple Pay
Apple предоставляет sandbox среды для тестирования транзакций Apple Pay:
// Включить режим sandbox в iOS приложении
#if DEBUG
let configuration = PKPaymentAuthorizationConfiguration()
configuration.merchantIdentifier = "merchant.com.yourapp.sandbox"
configuration.supportedNetworks = [.visa, .masterCard, .amex]
configuration.merchantCapabilities = .capability3DS
#else
// Продакшн конфигурация
#endif
Конфигурация Тестовых Карт
Apple предоставляет тестовые карты через Sandbox среду:
Тип Карты | Номер Тестовой Карты | Ожидаемое Поведение |
---|---|---|
Visa | 4111111111111111 | Успешный платеж |
Mastercard | 5555555555554444 | Успешный платеж |
Amex | 378282246310005 | Успешный платеж |
Отклонена | 4000000000000002 | Отклоненная транзакция |
Критические Тестовые Сценарии для Apple Pay
1. Поток Авторизации Платежа
func testApplePayAuthorization() {
let request = PKPaymentRequest()
request.merchantIdentifier = "merchant.com.yourapp"
request.supportedNetworks = [.visa, .masterCard, .amex]
request.merchantCapabilities = .capability3DS
request.countryCode = "US"
request.currencyCode = "USD"
let item = PKPaymentSummaryItem(label: "Тестовый Продукт", amount: NSDecimalNumber(string: "9.99"))
request.paymentSummaryItems = [item]
// Проверить представление контроллера авторизации
let controller = PKPaymentAuthorizationController(paymentRequest: request)
controller.delegate = self
controller.present { presented in
XCTAssertTrue(presented, "Контроллер платежа должен быть представлен")
}
}
2. Тестирование Биометрической Аутентификации
Тестировать Apple Pay с Face ID/Touch ID:
- Валидная биометрическая аутентификация
- Неудачные попытки аутентификации
- Откат к вводу пароля
- Отключенные биометрические настройки
3. Тестирование Совместимости Устройств
Проверить поддержку Apple Pay на:
- Моделях iPhone (iPhone 6 и новее)
- Интеграции Apple Watch
- Моделях iPad с Touch ID/Face ID
- Mac с Touch ID
Чек-лист Валидации Apple Pay
- Sandbox среда настроена корректно
- Тестовый идентификатор мерчанта активен
- Лист платежа отображает корректную сумму и имя мерчанта
- Поддерживаемые сети карт настроены правильно
- Аутентификация 3D Secure работает
- Квитанция транзакции генерируется корректно
- Обработка ошибок для отклоненных платежей
- Сценарии таймаута сети обработаны
- Валидация адреса доставки (если применимо)
- Сбор контактной информации работает
Стратегии Тестирования Google Pay
Google Pay предлагает более гибкую тестовую среду по сравнению с Apple Pay, с обширными возможностями sandbox.
Настройка Тестовой Среды Google Pay
// Конфигурация Google Pay в Android
private fun createPaymentsClient(): PaymentsClient {
val walletOptions = Wallet.WalletOptions.Builder()
.setEnvironment(WalletConstants.ENVIRONMENT_TEST) // Использовать тестовую среду
.build()
return Wallet.getPaymentsClient(this, walletOptions)
}
private fun createPaymentDataRequest(): PaymentDataRequest {
val request = PaymentDataRequest.fromJson(
"""
{
"apiVersion": 2,
"apiVersionMinor": 0,
"allowedPaymentMethods": [
{
"type": "CARD",
"parameters": {
"allowedAuthMethods": ["PAN_ONLY", "CRYPTOGRAM_3DS"],
"allowedCardNetworks": ["MASTERCARD", "VISA"]
},
"tokenizationSpecification": {
"type": "PAYMENT_GATEWAY",
"parameters": {
"gateway": "example",
"gatewayMerchantId": "exampleMerchantId"
}
}
}
],
"merchantInfo": {
"merchantId": "TEST_MERCHANT_ID",
"merchantName": "Тестовый Мерчант"
},
"transactionInfo": {
"totalPriceStatus": "FINAL",
"totalPrice": "12.34",
"currencyCode": "USD"
}
}
""".trimIndent()
)
return request
}
Тестовые Карты Google Pay
Google предоставляет комплексные тестовые карты для различных сценариев:
Сценарий | Тестовая Карта | CVV | ZIP |
---|---|---|---|
Успех | 4111111111111111 | 123 | 12345 |
Отклонена - Недостаточно Средств | 4000000000009995 | 123 | 12345 |
Отклонена - Утерянная Карта | 4000000000009987 | 123 | 12345 |
Аутентификация 3D Secure | 4000000000003220 | 123 | 12345 |
Ключевые Тестовые Сценарии Google Pay
1. Доступность Метода Платежа
@Test
fun testGooglePayAvailability() {
val isReadyToPayRequest = IsReadyToPayRequest.fromJson(
"""
{
"apiVersion": 2,
"apiVersionMinor": 0,
"allowedPaymentMethods": [
{
"type": "CARD",
"parameters": {
"allowedAuthMethods": ["PAN_ONLY", "CRYPTOGRAM_3DS"],
"allowedCardNetworks": ["MASTERCARD", "VISA"]
}
}
]
}
""".trimIndent()
)
paymentsClient.isReadyToPay(isReadyToPayRequest)
.addOnCompleteListener { task ->
assertTrue("Google Pay должен быть доступен", task.isSuccessful)
}
}
2. Валидация Токена
Проверить структуру и шифрование платежного токена:
fun validatePaymentToken(token: String) {
val jsonToken = JSONObject(token)
// Проверить обязательные поля
assertTrue(jsonToken.has("signature"))
assertTrue(jsonToken.has("protocolVersion"))
assertTrue(jsonToken.has("signedMessage"))
// Проверить валидность подписи
val signedMessage = jsonToken.getString("signedMessage")
assertNotNull(signedMessage)
assertFalse(signedMessage.isEmpty())
}
Тестирование Встроенных Покупок (IAP) для iOS и Android
Встроенные покупки требуют платформо-специфичных подходов тестирования для расходуемых, нерасходуемых, подписок и автообновляемого контента.
Тестирование Встроенных Покупок iOS
Конфигурация Тестера Sandbox:
- Создать тестовые аккаунты sandbox в App Store Connect
- Настроить разные типы аккаунтов (новые пользователи, существующие подписчики)
- Тестировать с различными регионами аккаунтов для локализации
// Тестирование StoreKit в iOS
import StoreKit
class IAPManager: NSObject, SKPaymentTransactionObserver {
func purchaseProduct(productId: String) {
guard SKPaymentQueue.canMakePayments() else {
print("Пользователь не может совершать платежи")
return
}
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
}
func paymentQueue(_ queue: SKPaymentQueue,
updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .purchased:
// Проверить квитанцию и разблокировать контент
completeTransaction(transaction)
case .failed:
// Обработать сбой
failedTransaction(transaction)
case .restored:
// Восстановить предыдущую покупку
restoreTransaction(transaction)
case .deferred:
// Платеж ожидает одобрения
print("Платеж отложен")
case .purchasing:
print("Покупка...")
@unknown default:
break
}
}
}
}
Тестовые Кейсы IAP для iOS:
- Получение продуктов из App Store
- Завершение потока покупки
- Валидация квитанции (локальная и серверная)
- Восстановление транзакций
- Прерванные транзакции (завершение приложения во время покупки)
- Поддержка Семейного Доступа
- Промо-предложения и вступительные цены
Тестирование Встроенной Оплаты Android
Библиотека Оплаты Google Play предоставляет комплексные инструменты тестирования:
class BillingManager(private val context: Context) : PurchasesUpdatedListener {
private lateinit var billingClient: BillingClient
fun initializeBilling() {
billingClient = BillingClient.newBuilder(context)
.setListener(this)
.enablePendingPurchases()
.build()
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(result: BillingResult) {
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
// Запросить доступные продукты
queryProducts()
}
}
override fun onBillingServiceDisconnected() {
// Повторить попытку подключения
}
})
}
override fun onPurchasesUpdated(result: BillingResult, purchases: List<Purchase>?) {
when (result.responseCode) {
BillingClient.BillingResponseCode.OK -> {
purchases?.forEach { purchase ->
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
// Подтвердить покупку
acknowledgePurchase(purchase)
}
}
}
BillingClient.BillingResponseCode.USER_CANCELED -> {
// Обработать отмену пользователем
}
else -> {
// Обработать другие ошибки
}
}
}
}
Тестовые ID Покупок Android:
Google предоставляет зарезервированные ID продуктов для тестирования:
android.test.purchased
- Всегда успешная покупкаandroid (как обсуждается в [Detox: Grey-Box Testing for React Native Applications](/blog/detox-react-native-grey-box)).test.canceled
- Симулирует отмененную покупкуandroid.test.refunded
- Симулирует возвращенную покупкуandroid.test.item_unavailable
- Симулирует недоступный продукт
Тестирование Интеграции Платежного Шлюза
Интеграция платежного шлюза требует тестирования коммуникации между вашим приложением, бэкенд-сервером и платежным процессором.
Чек-лист Тестирования Платежного Шлюза
Точки Интеграции для Тестирования:
// Пример обработки платежей на бэкенде (Node.js)
const stripe = require('stripe')(process.env.STRIPE_TEST_KEY);
async function processPayment(paymentData) {
try {
// Создать намерение платежа
const paymentIntent = await stripe.paymentIntents.create({
amount: paymentData.amount,
currency: 'usd',
payment_method_types: ['card'],
metadata: {
orderId: paymentData.orderId,
userId: paymentData.userId
}
});
// Подтвердить платеж
const confirmedPayment = await stripe.paymentIntents.confirm(
paymentIntent.id,
{
payment_method: paymentData.paymentMethodId
}
);
return {
success: true,
transactionId: confirmedPayment.id,
status: confirmedPayment.status
};
} catch (error) {
return {
success: false,
error: error.message,
code: error.code
};
}
}
Распространенные Платежные Шлюзы и Тестовые Среды
Шлюз | Настройка Тестового Режима | Предоставленные Тестовые Карты |
---|---|---|
Stripe | Использовать тестовые API ключи с префиксом sk_test_ | Комплексный набор тестовых карт |
PayPal | Sandbox среда PayPal | Требуются sandbox аккаунты |
Braintree | Sandbox ID мерчанта | Предоставлены тестовые номера карт |
Square | Sandbox токен доступа | Тестовые карты для различных сценариев |
Adyen | Тестовый аккаунт мерчанта | Тестовые номера карт по регионам |
Сценарии Тестирования API Интеграции
# Пример интеграционного теста на Python
import unittest
import requests
class PaymentGatewayTests(unittest.TestCase):
def setUp(self):
self.base_url = "https://api.payment-gateway.test"
self.api_key = "test_api_key_12345"
def test_successful_payment(self):
"""Тестировать успешную обработку платежа"""
payload = {
"amount": 1000, # Сумма в центах
"currency": "USD",
"card": {
"number": "4242424242424242",
"exp_month": 12,
"exp_year": 2025,
"cvc": "123"
}
}
response = requests.post(
f"{self.base_url}/charges",
json=payload,
headers={"Authorization": f"Bearer {self.api_key}"}
)
self.assertEqual(response.status_code, 200)
self.assertTrue(response.json()["success"])
def test_insufficient_funds(self):
"""Тестировать отклонение платежа из-за недостатка средств"""
payload = {
"amount": 1000,
"currency": "USD",
"card": {
"number": "4000000000009995", # Отклоненная карта
"exp_month": 12,
"exp_year": 2025,
"cvc": "123"
}
}
response = requests.post(
f"{self.base_url}/charges",
json=payload,
headers={"Authorization": f"Bearer {self.api_key}"}
)
self.assertEqual(response.status_code, 402)
self.assertEqual(response.json()["error_code"], "insufficient_funds")
Тестирование Соответствия PCI DSS
Соответствие PCI DSS (Стандарт Безопасности Данных Индустрии Платежных Карт) обязательно для приложений, обрабатывающих данные платежных карт.
Ключевые Требования PCI DSS для Мобильных Приложений
1. Никогда не Хранить Чувствительные Данные Аутентификации:
- Коды CVV/CVC никогда не должны храниться
- Полные данные магнитной полосы запрещены
- Блоки PIN не должны сохраняться
2. Шифровать Данные Держателя Карты:
// Пример шифрования в iOS
import CryptoKit
func encryptCardData(_ cardNumber: String, publicKey: String) throws -> Data {
let key = try P256.KeyAgreement.PublicKey(pemRepresentation: publicKey)
let symmetricKey = SymmetricKey(size: .bits256)
let sealedBox = try AES.GCM.seal(
cardNumber.data(using: .utf8)!,
using: symmetricKey
)
return sealedBox.combined!
}
3. Реализовать Строгие Контроли Доступа:
- Ролевой доступ к платежным данным
- Многофакторная аутентификация для админ доступа
- Аудит логирование платежных операций
Чек-лист Тестирования PCI DSS
- Нет данных карты в открытом виде в базах данных
- Нет чувствительных данных в логах или сообщениях об ошибках
- SSL/TLS шифрование для передачи данных (TLS 1.2+)
- Токенизация реализована для хранения карт
- Память очищена после обработки платежа
- Нет данных карт в отчетах о сбоях или аналитике
- Практики безопасного кодирования соблюдены
- Регулярное сканирование безопасности выполняется
- Тестирование на проникновение проводится ежегодно
- Документация процедур безопасности поддерживается
Тестирование Утечки Данных
# Проверить чувствительные данные в логах
grep -r "4[0-9]{15}" ./logs/ # Искать возможные номера кредитных карт
grep -r "cvv\|cvc" ./logs/ # Искать упоминания CVV
# Анализировать трафик приложения с прокси
mitmproxy -p 8080 # Перехватывать HTTPS трафик для проверки шифрования
Sandbox и Тестовые Среды
Эффективное использование sandbox сред критично для комплексного тестирования платежей без финансового риска.
Лучшие Практики для Тестирования в Sandbox
1. Изоляция Среды:
# Управление конфигурацией
environments:
development:
payment_gateway: "sandbox"
api_key: "${SANDBOX_API_KEY}"
endpoint: "https://sandbox.payment-gateway.com"
staging:
payment_gateway: "sandbox"
api_key: "${STAGING_API_KEY}"
endpoint: "https://sandbox.payment-gateway.com"
production:
payment_gateway: "production"
api_key: "${PRODUCTION_API_KEY}"
endpoint: "https://api.payment-gateway.com"
2. Флаги Функций для Тестирования Платежей:
// Реализация флага функции
const config = {
useSandbox: process.env.NODE_ENV !== 'production',
enableTestCards: process.env.ENABLE_TEST_CARDS === 'true',
skipReceiptValidation: process.env.SKIP_RECEIPT_VALIDATION === 'true'
};
function getPaymentClient() {
return config.useSandbox
? new SandboxPaymentClient(config.sandboxApiKey)
: new ProductionPaymentClient(config.productionApiKey);
}
Ограничения Sandbox для Рассмотрения
Распространенные ограничения sandbox:
- Ограниченная поддержка вебхуков и коллбэков
- Сниженные лимиты частоты по сравнению с продакшеном
- Неполное соответствие функций (некоторые продвинутые функции недоступны)
- Сохранение данных может быть временным
- Симуляция 3D Secure может быть упрощена
Обходные пути:
- Mock сервер для тестирования вебхуков
- Тестирование лимитов частоты в продакшен-подобной среде
- Документировать известные ограничения sandbox в планах тестирования
Тестирование Безопасности для Платежных Потоков
Тестирование безопасности для платежных систем требует специализированных техник помимо стандартного тестирования безопасности приложений.
Тестирование Атак Man-in-the-Middle (MITM)
# Тест certificate pinning
import ssl
import socket
def test_certificate_pinning(hostname, port, expected_cert_hash):
"""Проверить, что приложение реализует certificate pinning"""
context = ssl.create_default_context()
with socket.create_connection((hostname, port)) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
cert_der = ssock.getpeercert(binary_form=True)
cert_hash = hashlib.sha256(cert_der).hexdigest()
assert cert_hash == expected_cert_hash, \
"Хеш сертификата не совпадает - возможная MITM атака"
Тестирование Утечки Чувствительных Данных
Тестировать утечку данных в различных местах:
# Инспекция iOS Keychain
security dump-keychain ~/Library/Keychains/login.keychain-db
# Проверка SharedPreferences в Android
adb shell run-as com.yourapp.package cat /data/data/com.yourapp.package/shared_prefs/preferences.xml
# Анализ дампа памяти
fridump -U com.yourapp.package -s
grep -r "4[0-9]{15}" dump/ # Искать номера карт в памяти
Тестирование Безопасности Сессии
Тестовые кейсы для безопасности платежной сессии:
- Таймаут сессии после бездействия
- Инвалидация сессии после завершения платежа
- Предотвращение одновременных сессий
- Валидация CSRF токена
- Предотвращение replay атак
// Тест управления сессией
describe('Безопасность Платежной Сессии', () => {
it('должна истекать сессия после 15 минут бездействия', async () => {
const session = await createPaymentSession();
// Ждать периода таймаута
await sleep(15 * 60 * 1000);
// Попытаться использовать истекшую сессию
const result = await processPayment(session.id);
expect(result.error).toBe('session_expired');
});
it('должна предотвращать replay атаки', async () => {
const payment = await capturePaymentRequest();
// Обработать платеж
await processPayment(payment);
// Попытаться replay того же платежа
const replayResult = await processPayment(payment);
expect(replayResult.error).toBe('duplicate_transaction');
});
});
Тестирование Сценариев Отказа Платежа
Тщательное тестирование сценариев отказа необходимо для надежных платежных систем.
Распространенные Сценарии Отказа
1. Сетевые Отказы:
// Симулировать сетевой таймаут
@Test
fun testPaymentNetworkTimeout() {
// Замокать сетевой слой для симуляции таймаута
mockWebServer.enqueue(MockResponse().setSocketPolicy(SocketPolicy.NO_RESPONSE))
val result = paymentService.processPayment(testPaymentData)
assertTrue(result is PaymentResult.NetworkError)
assertEquals("Request timeout", result.message)
}
2. Недостаточно Средств:
func testInsufficientFunds() {
let testCard = TestCard(number: "4000000000009995") // Отклоненная карта
let expectation = XCTestExpectation(description: "Платеж отклонен")
paymentProcessor.process(card: testCard, amount: 100.00) { result in
switch result {
case .failure(let error):
XCTAssertEqual(error, .insufficientFunds)
expectation.fulfill()
case .success:
XCTFail("Платеж должен был быть отклонен")
}
}
wait(for: [expectation], timeout: 10.0)
}
3. Неверные Данные Карты:
Тестовый Сценарий | Тестовая Карта | Ожидаемый Результат |
---|---|---|
Неверный номер | 4242424242424241 | Ошибка валидации карты |
Истекшая карта | Exp: 01/2020 | Ошибка истекшей карты |
Неверный CVV | CVV: 99 | Ошибка неверного CVV |
Неверный ZIP | ZIP: 00000 | Ошибка несовпадения ZIP |
Тестирование Восстановления от Ошибок
// Тестировать логику автоматического повтора
async function testPaymentRetry() {
let attempts = 0;
const maxRetries = 3;
// Замокать платежный сервис для двух отказов, успех на третьей попытке
paymentService.process = jest.fn()
.mockRejectedValueOnce(new Error('Временный отказ'))
.mockRejectedValueOnce(new Error('Временный отказ'))
.mockResolvedValueOnce({ success: true, transactionId: '12345' });
const result = await paymentWithRetry(paymentData, maxRetries);
expect(paymentService.process).toHaveBeenCalledTimes(3);
expect(result.success).toBe(true);
}
Тестирование Подписок и Повторяющихся Платежей
Модели подписок требуют специализированного тестирования для различных событий жизненного цикла.
Сценарии Тестирования Жизненного Цикла Подписки
1. Создание Новой Подписки:
func testSubscriptionCreation() {
let subscription = Subscription(
productId: "monthly_premium",
period: .monthly,
price: 9.99
)
subscriptionManager.subscribe(to: subscription) { result in
XCTAssertTrue(result.isSuccess)
XCTAssertNotNil(result.receiptData)
XCTAssertEqual(result.expirationDate, expectedDate)
}
}
2. Тестирование Продления:
@Test
fun testAutoRenewal() = runTest {
// Создать подписку, истекающую скоро
val subscription = createTestSubscription(
expiresAt = now().plusMinutes(5)
)
// Ждать окна автопродления
advanceTimeBy(6.minutes)
// Проверить продленную подписку
val renewed = subscriptionRepository.getSubscription(subscription.id)
assertTrue(renewed.isActive)
assertEquals(now().plusMonths(1), renewed.expiresAt)
}
3. Повышение/Понижение Подписки:
Сценарий | С Плана | На План | Ожидаемое Поведение |
---|---|---|---|
Повышение | Базовый ($9.99) | Премиум ($19.99) | Немедленное повышение, пропорциональная оплата |
Понижение | Премиум ($19.99) | Базовый ($9.99) | Изменение в конце периода |
Переключение | Месячный ($9.99) | Годовой ($99.99) | Применен пропорциональный кредит |
Тестирование Циклов Оплаты
def test_billing_cycle():
"""Тестировать оплату подписки через несколько циклов"""
subscription = create_subscription(
user_id="test_user",
plan="monthly",
start_date=datetime(2025, 1, 1)
)
# Тестировать первую оплату
first_charge = process_billing_cycle(subscription, datetime(2025, 1, 1))
assert first_charge.amount == 9.99
assert first_charge.status == "succeeded"
# Тестировать вторую оплату
second_charge = process_billing_cycle(subscription, datetime(2025, 2, 1))
assert second_charge.amount == 9.99
assert second_charge.status == "succeeded"
# Тестировать неудачный платеж
set_payment_method_invalid(subscription)
third_charge = process_billing_cycle(subscription, datetime(2025, 3, 1))
assert third_charge.status == "failed"
assert subscription.status == "past_due"
Льготный Период и Логика Повтора
describe('Льготный Период Подписки', () => {
it('должен повторять неудачные платежи во время льготного периода', async () => {
const subscription = await createSubscription({
gracePeriodDays: 7,
retryAttempts: 3
});
// Симулировать неудачный платеж
await simulateFailedPayment(subscription);
// Проверить статус подписки
expect(subscription.status).toBe('past_due');
expect(subscription.gracePeriodEnds).toBe(addDays(now(), 7));
// Симулировать попытки повтора
for (let i = 1; i <= 3; i++) {
await advanceTime(2, 'days');
const retryResult = await retryPayment(subscription);
if (i < 3) {
expect(retryResult.attempt).toBe(i);
expect(subscription.status).toBe('past_due');
}
}
// Проверить отмену подписки после льготного периода
await advanceTime(1, 'days');
expect(subscription.status).toBe('cancelled');
});
});
Тестирование Возвратов и Отмены
Обработка возвратов и рабочие процессы отмены требуют тщательного тестирования для обеспечения корректной финансовой сверки.
Сценарии Тестирования Возврата
class RefundTests(unittest.TestCase):
def test_full_refund(self):
"""Тестировать полный возврат платежа"""
# Создать и обработать платеж
payment = create_payment(amount=100.00)
process_payment(payment)
# Обработать полный возврат
refund = create_refund(
payment_id=payment.id,
amount=100.00,
reason="customer_request"
)
self.assertEqual(refund.status, "succeeded")
self.assertEqual(refund.amount, 100.00)
self.assertEqual(payment.status, "refunded")
def test_partial_refund(self):
"""Тестировать частичный возврат платежа"""
payment = create_payment(amount=100.00)
process_payment(payment)
# Обработать частичный возврат
refund = create_refund(
payment_id=payment.id,
amount=50.00,
reason="partial_return"
)
self.assertEqual(refund.status, "succeeded")
self.assertEqual(refund.amount, 50.00)
self.assertEqual(payment.amount_refunded, 50.00)
self.assertEqual(payment.status, "partially_refunded")
def test_refund_deadline(self):
"""Тестировать что возврат не может быть обработан после дедлайна"""
# Создать платеж 6 месяцев назад
payment = create_payment(
amount=100.00,
created_at=datetime.now() - timedelta(days=180)
)
# Попытаться вернуть
with self.assertRaises(RefundError) as context:
create_refund(payment_id=payment.id, amount=100.00)
self.assertIn("refund_deadline_exceeded", str(context.exception))
Тестирование Отмены
func testSubscriptionCancellation() {
let subscription = createActiveSubscription()
// Тестировать немедленную отмену
subscriptionManager.cancel(subscription, immediately: true) { result in
XCTAssertTrue(result.isSuccess)
XCTAssertEqual(subscription.status, .cancelled)
XCTAssertNil(subscription.nextBillingDate)
XCTAssertTrue(subscription.accessExpiresAt <= Date())
}
// Тестировать отмену в конце периода
let subscription2 = createActiveSubscription()
subscriptionManager.cancel(subscription2, immediately: false) { result in
XCTAssertTrue(result.isSuccess)
XCTAssertEqual(subscription2.status, .cancelledAtPeriodEnd)
XCTAssertNotNil(subscription2.accessExpiresAt)
XCTAssertTrue(subscription2.accessExpiresAt > Date())
}
}
Тестирование Множественных Валют и Локализации
Мобильные приложения, обслуживающие глобальную аудиторию, должны корректно обрабатывать множественные валюты и локализацию.
Сценарии Тестирования Валют
const currencyTestCases = [
{ currency: 'USD', amount: 100.00, formatted: '$100.00' },
{ currency: 'EUR', amount: 100.00, formatted: '100,00 €' },
{ currency: 'GBP', amount: 100.00, formatted: '£100.00' },
{ currency: 'JPY', amount: 10000, formatted: '¥10,000' },
{ currency: 'RUB', amount: 7500, formatted: '7 500 ₽' }
];
describe('Форматирование Валют', () => {
currencyTestCases.forEach(testCase => {
it(`должно форматировать ${testCase.currency} корректно`, () => {
const formatted = formatCurrency(
testCase.amount,
testCase.currency
);
expect(formatted).toBe(testCase.formatted);
});
});
});
Тестирование Обменного Курса
@Test
fun testCurrencyConversion() {
val converter = CurrencyConverter()
// Замокать сервис обменного курса
whenever(exchangeRateService.getRate("USD", "EUR"))
.thenReturn(0.85)
val result = converter.convert(
amount = 100.0,
from = "USD",
to = "EUR"
)
assertEquals(85.0, result, 0.01)
}
@Test
fun testPriceConsistency() {
// Обеспечить согласованность цен между валютами
val usdPrice = 9.99
val eurPrice = 8.49
val gbpPrice = 7.99
val currentRate = exchangeRateService.getRate("USD", "EUR")
val convertedPrice = usdPrice * currentRate
// Допускать 5% отклонение для округления цен
assertTrue(abs(convertedPrice - eurPrice) / eurPrice < 0.05)
}
Тестирование Региональных Методов Платежа
Разные регионы поддерживают разные методы платежа:
Регион | Методы Платежа | Требования Тестирования |
---|---|---|
Северная Америка | Кредитные карты, Apple Pay, Google Pay, PayPal | Тестировать все основные сети |
Европа | SEPA, Кредитные карты, Apple Pay, Google Pay, Bancontact | Поток прямого дебета SEPA |
Азия | Alipay, WeChat Pay, Кредитные карты, Line Pay | Поток платежа QR кодом |
Латинская Америка | Кредитные карты, Mercado Pago, PIX, OXXO | Ваучеры наличной оплаты |
Автоматизированное Тестирование Платежей и CI/CD
Интеграция платежных тестов в CI/CD пайплайны обеспечивает непрерывную валидацию платежной функциональности.
Структура Автоматизированной Тестовой Сюиты
# .github/workflows/payment-tests.yml
name: Интеграционные Тесты Платежей
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
payment-tests:
runs-on: ubuntu-latest
env:
STRIPE_TEST_KEY: ${{ secrets.STRIPE_TEST_KEY }}
PAYPAL_SANDBOX_CLIENT: ${{ secrets.PAYPAL_SANDBOX_CLIENT }}
steps:
- uses: actions/checkout@v3
- name: Настроить Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Установить зависимости
run: npm ci
- name: Запустить юнит тесты платежей
run: npm run test:payment:unit
- name: Запустить интеграционные тесты платежей
run: npm run test:payment:integration
- name: Запустить тесты безопасности платежей
run: npm run test:payment:security
- name: Сгенерировать отчет о тестах платежей
if: always()
run: npm run test:report
- name: Загрузить результаты тестов
if: always()
uses: actions/upload-artifact@v3
with:
name: payment-test-results
path: test-results/
Mock Платежный Сервер для Тестирования
// mock-payment-server.js
const express = require('express');
const app = express();
app.post('/v1/charges', (req, res) => {
const { card, amount } = req.body;
// Симулировать различные сценарии карт
const testCards = {
'4242424242424242': { success: true },
'4000000000000002': { success: false, error: 'card_declined' },
'4000000000009995': { success: false, error: 'insufficient_funds' },
'4000000000000069': { success: false, error: 'expired_card' }
};
const result = testCards[card.number] || { success: true };
if (result.success) {
res.json({
id: `ch_test_${Date.now()}`,
amount: amount,
status: 'succeeded',
paid: true
});
} else {
res.status(402).json({
error: {
type: 'card_error',
code: result.error,
message: getErrorMessage(result.error)
}
});
}
});
app.listen(3001, () => {
console.log('Mock платежный сервер запущен на порту 3001');
});
Тестирование Производительности для Платежных Потоков
from locust import HttpUser, task, between
class PaymentLoadTest(HttpUser):
wait_time = between(1, 3)
@task(3)
def create_payment_intent(self):
"""Тестировать создание намерения платежа под нагрузкой"""
self.client.post("/v1/payment_intents", json={
"amount": 1000,
"currency": "usd",
"payment_method_types": ["card"]
}, headers={"Authorization": f"Bearer {self.api_key}"})
@task(2)
def confirm_payment(self):
"""Тестировать подтверждение платежа под нагрузкой"""
# Сначала создать намерение платежа
response = self.client.post("/v1/payment_intents", json={
"amount": 1000,
"currency": "usd"
}, headers={"Authorization": f"Bearer {self.api_key}"})
if response.status_code == 200:
intent_id = response.json()["id"]
# Подтвердить платеж
self.client.post(f"/v1/payment_intents/{intent_id}/confirm",
json={"payment_method": "pm_card_visa"},
headers={"Authorization": f"Bearer {self.api_key}"})
@task(1)
def retrieve_payment(self):
"""Тестировать производительность получения платежа"""
self.client.get("/v1/charges/ch_test_12345",
headers={"Authorization": f"Bearer {self.api_key}"})
Непрерывный Мониторинг
// Мониторинг здоровья платежей в продакшене
const monitor = {
async checkPaymentHealth() {
const metrics = {
successRate: await this.calculateSuccessRate(),
avgProcessingTime: await this.getAvgProcessingTime(),
failureReasons: await this.getFailureBreakdown()
};
// Оповестить если уровень успеха падает ниже порога
if (metrics.successRate < 0.95) {
this.sendAlert({
severity: 'high',
message: `Уровень успеха платежей упал до ${metrics.successRate}`,
metrics: metrics
});
}
return metrics;
},
async calculateSuccessRate() {
const last24h = new Date(Date.now() - 24 * 60 * 60 * 1000);
const payments = await Payment.find({
createdAt: { $gte: last24h }
});
const successful = payments.filter(p => p.status === 'succeeded').length;
return successful / payments.length;
}
};
// Запускать проверку здоровья каждые 5 минут
setInterval(() => monitor.checkPaymentHealth(), 5 * 60 * 1000);
Заключение
Тестирование мобильных платежей требует комплексного подхода, охватывающего платформо-специфичные реализации, соответствие безопасности, sandbox среды и различные платежные сценарии. Ключевые выводы для эффективного тестирования платежей:
- Интенсивно использовать sandbox среды - Использовать тестовые среды от Apple, Google и платежных шлюзов для валидации функциональности без финансового риска
- Тестировать все сценарии отказа - Сетевые проблемы, отклоненные карты, таймауты и граничные случаи должны быть тщательно протестированы
- Приоритизировать тестирование безопасности - Соответствие PCI DSS, шифрование данных и безопасная передача критичны
- Автоматизировать где возможно - Интегрировать платежные тесты в CI/CD пайплайны для непрерывной валидации
- Тестировать кросс-платформенно и кросс-регионально - Разные платформы, валюты и региональные методы платежа требуют специфического тестового покрытия
- Мониторить продакшн метрики - Отслеживать уровни успеха платежей, время обработки и паттерны отказов в реальном времени
Следуя этим руководствам и реализуя комплексное тестовое покрытие, QA инженеры могут обеспечить надежные, безопасные и стабильные мобильные платежные системы, предоставляющие отличный пользовательский опыт при поддержании строгих стандартов безопасности и соответствия.