Генерація QR для криптоплатежів у мобільному додатку
Уявіть: користувач сканує ваш QR, але гаманець видає «невідома валюта» або не підставляє суму. Причина — невірний URI-формат або невідповідний рівень корекції помилок. Ми налаштовували генерацію QR для десятків криптогаманців і знаємо, як уникнути цих проблем. Коректний URI (BIP-21, EIP-681) та правильний Error Correction Level — ключ до безшовної оплати. Без них користувач вводить дані вручну, зростає кількість помилок і знижується конверсія. Зв'яжіться з нами — ми допоможемо впровадити надійне рішення під ваш стек.
Використовуємо перевірені стеки: Swift + CoreImage на iOS, Kotlin + ZXing на Android. CoreImage генерує QR швидше (на 30% за тестами), але ZXing дає більше контролю над модулями — це особливо важливо при нестандартних розмірах. Вибір залежить від задачі: якщо потрібна швидкість — CoreImage, якщо кастомізація — ZXing.
Чому URI-формат визначає успіх платежу?
Стандартні URI (BIP-21, EIP-681) розпізнаються більшістю гаманців. Якщо опустити параметр amount або вказати його в невірних одиницях (наприклад, сатоші замість біткоїнів), платнику доведеться вводити суму вручну. Це збільшує ризик помилки та знижує конверсію.
BIP-21 — стандарт формату URI для Bitcoin. EIP-681 — стандарт для Ethereum.
URI-формати для різних блокчейнів
| Блокчейн |
Формат |
Приклад |
| Bitcoin (BIP-21) |
bitcoin:<address>?amount=<BTC>&label=<text> |
bitcoin:1A2B3C4D5E6F?amount=0.001&label=Order+123 |
| Ethereum (EIP-681) |
ethereum:<address>@<chainID>?value=<wei> |
ethereum:0xAbCd1234@1?value=500000000000000000 |
| ERC-20 (EIP-681) |
ethereum:<tokenContract>@<chainID>/transfer?address=<recipient>&uint256=<amount> |
ethereum:0xTokenAddress@1/transfer?address=0xRecipient&uint256=1000000 |
| Solana (SPL) |
solana:<recipient>?amount=<SOL>&spl-token=<mint>&label=<text> |
solana:RecipientPubkey?amount=0.5&spl-token=TokenMintAddress&label=Payment |
Для Solana потрібно вказати mint токена; якщо не вказано, приймається SOL. Це важливо врахувати при мультичейн-реалізації.
EIP-681 для ERC-20 — нотація неочевидна: спочатку адреса контракту токена, потім метод transfer з отримувачем та сумою. Багато гаманців підтримують спрощений формат: ethereum:0xRecipient?value=X&contractAddress=0xToken.
Який рівень корекції QR вибрати?
Вибір рівня корекції — компроміс між стійкістю до пошкоджень та щільністю даних. Для мобільних застосунків з брендуванням ми рекомендуємо рівень Q (25%). Він дає в 1,5 рази більшу стійкість, ніж M, при незначному збільшенні розміру.
| Рівень |
Відновлення даних |
Підходить для логотипа |
Розмір QR (при 40 символах) |
| L |
~7% |
Ні |
21×21 модулів |
| M |
~15% |
З обережністю |
25×25 |
| Q |
~25% |
Так, до 30% площі |
29×29 |
| H |
~30% |
Так |
33×33 |
Без логотипа достатньо M (15%) — він забезпечує хорошу читабельність при малих кутах сканування. Але при додаванні логотипа без підвищення корекції до Q читабельність падає на 40%.
Генерація QR з правильними параметрами
// iOS — генерація QR через CoreImage з потрібним рівнем корекції
import CoreImage.CIFilterBuiltins
func generateQRCode(from string: String, size: CGFloat = 300) -> UIImage {
let filter = CIFilter.qrCodeGenerator()
filter.message = Data(string.utf8)
filter.correctionLevel = "Q" // 25% error correction — для логотипа поверх QR
let transform = CGAffineTransform(scaleX: size / filter.outputImage!.extent.width,
y: size / filter.outputImage!.extent.height)
let scaledImage = filter.outputImage!.transformed(by: transform)
// Інтерполяція nearest neighbor для чіткості
let context = CIContext()
let cgImage = context.createCGImage(scaledImage, from: scaledImage.extent)!
return UIImage(cgImage: cgImage)
}
// Android — ZXing з кастомним розміром
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
val hints = mapOf(
EncodeHintType.ERROR_CORRECTION to ErrorCorrectionLevel.Q,
EncodeHintType.MARGIN to 1,
EncodeHintType.CHARACTER_SET to "UTF-8"
)
val bitMatrix = QRCodeWriter().encode(uri, BarcodeFormat.QR_CODE, 512, 512, hints)
Динамічна сума та вибір токена
Якщо в застосунку є поле введення суми перед генерацією QR — URI оновлюється при кожній зміні суми. Для ERC-20 важливо врахувати decimals при формуванні value:
// Android — розрахунок value для USDC (6 decimals)
val humanAmount = BigDecimal("100.50") // введено користувачем
val decimals = 6
val rawAmount = humanAmount.movePointRight(decimals).toBigInteger() // 100500000
val uri = "ethereum:${usdcContractAddress}@1/transfer?address=${recipientAddress}&uint256=$rawAmount"
Помилка в decimals — одна з частих причин невірного переказу. Для USDC (6 decimals) сума 100.50 USDC перетворюється на 100500000 одиниць. Для ETH (18 decimals) — 100500000000000000000 wei.
Брендування поверх QR
Логотип застосунку в центрі QR — стандартна практика. Центральна зона до 30% площі — безпечно при рівні корекції Q. Логотип має бути білим квадратом з іконкою, щоб не порушувати контрастність модулів QR.
Що робити при проблемах зі скануванням?
Якщо QR не зчитується, перевірте рівень корекції (має бути Q або H при логотипі), контраст між модулями та фоном, а також розмір логотипа (не більше 30% площі). Переконайтеся, що URI відповідає стандарту для обраного блокчейну.
Як ми реалізуємо крипто-QR під ключ?
Наш процес включає:
- Аналіз — вибір блокчейнів, токенів, вимог до брендування.
- Проектування — підготовка URI-форматів, вибір бібліотек генерації.
- Реалізація — код на Swift / Kotlin з урахуванням динамічної суми та error correction.
- Тестування — сканування на 10+ популярних гаманцях (Trust Wallet, MetaMask, Coinbase).
- Деплой — інтеграція з App Store / Google Play, налаштування App Distribution.
Терміни: від 1 дня (один блокчейн, без логотипа) до 10 днів (мультичейн, брендування, вибір токенів). Вартість інтеграції під ключ розраховується індивідуально — зв'яжіться з нами для оцінки проекту.
Що входить в результат
- Генерація QR з коректним URI для кожного блокчейну.
- Динамічне оновлення суми та вибір токена.
- Брендування QR логотипом з рівнем корекції Q.
- Кнопки «Копіювати адресу» та «Поділитися QR».
- Вихідний код з коментарями та документація.
- Підтримка після релізу (1 місяць). Вартість підтримки включена в базовий пакет.
Чому варто довірити це завдання нам?
Ми займаємося мобільною розробкою більше 5 років, реалізували понад 30 проектів з криптовалютними платежами. Гарантуємо сумісність з актуальними версіями гаманців та магазинів застосунків. Досвід з проходженням App Store Review Guidelines (Section 4.2, 5.1) та Google Play Console — ваше застосунок не відхилять через реалізацію QR.
Замовте реалізацію — ми підготуємо все за 3–10 днів. Отримайте консультацію по вашому проекту прямо зараз.
Платежі в мобільних додатках: In-App Purchase, StoreKit 2, Google Billing, Stripe, RevenueCat
У кожному нашому проєкті з монетизації додатку ми балансуємо між політиками App Store та Google Play, вимогами PCI DSS та логікою верифікації покупок на бекенді. Неправильно зроблена система платежів — це не просто баг, це фінансові втрати та можливий бан додатку. За 7 років ми розібрали понад 50 кейсів інтеграції платіжних SDK — від простої Stripe-форми до розподіленого біллінгу з власними серверними вебхуками.
Як вибрати між In-App Purchase та зовнішнім платіжним шлюзом?
Якщо додаток продає цифровий контент або підписки — Apple та Google вимагають використовувати їхні платіжні системи. Обійти це неможливо: порушення правил 3.1.1 App Store або Google Play Developer Policy призводить до видалення додатку. Фізичні товари та послуги, що надаються офлайн, — інша історія.
In-App Purchase: дві платформи, два різних API
StoreKit 2 (iOS 15+)
StoreKit 2 — повна переробка оригінального StoreKit з async/await API. Product.products(for:), product.purchase(), Transaction.currentEntitlements — читабельно та передбачувано порівняно з чергою транзакцій через SKPaymentTransactionObserver.
Найважливіша зміна: транзакції в StoreKit 2 підписані JWS (JSON Web Signature) і верифікуються локально без серверного roundtrip. Transaction.verificationResult повертає .verified(Transaction) або .unverified(Transaction, VerificationError). Це не означає, що сервер не потрібен — він потрібен для зберігання статусу підписки, але локальна верифікація прибирає затримку при старті.
StoreKit.AppTransaction — верифікація самого факту завантаження додатку з App Store. Потрібна для додатків з платним завантаженням або безстроковими покупками, не підписками.
Складне місце в StoreKit 2 — обробка renewalState для підписок: .subscribed, .expired, .inBillingRetryPeriod, .inGracePeriod, .revoked. Стан inGracePeriod означає, що Apple намагається відновити оплату (до 16 днів) — у цей час потрібно продовжувати надавати доступ. Не обробиш — втратиш лояльних користувачів, у яких тимчасово не пройшла картка. За досвідом, близько 5% підписок потрапляють у billing retry, і автоматичне відновлення доступу повертає до 80% з них.
Google Play Billing Library (v6+)
Google Billing — складніший за StoreKit за кількістю сценаріїв. BillingClient з PurchasesUpdatedListener, queryProductDetailsAsync, launchBillingFlow, queryPurchasesAsync — обов'язково викликати при кожному старті додатку, не покладатися на PurchasesUpdatedListener як єдине джерело правди.
Підтвердження покупки: acknowledgePurchase() для non-consumables та підписок, consumePurchase() для consumables. Якщо не викликати acknowledge протягом 3 днів — Google автоматично зробить повернення. Це гарантована втрата грошей, якщо забути про acknowledge на бекенді після верифікації.
ProductDetails з SubscriptionOfferDetails — в Billing v5+ структура оферт ускладнилася: один продукт може мати кілька basePlanId та offerId (пробний період, знижка для нових користувачів, retention-офер). BillingFlowParams.SubscriptionUpdateParams для апгрейду/даунгрейду підписки з prorationMode.
Чому серверна верифікація обов'язкова?
Ніколи не довіряйте лише клієнтському коду при розблокуванні платного контенту. Клієнтська верифікація обходиться модифікацією додатку.
Для IAP мінімальна схема: додаток отримує receiptData (iOS) або purchaseToken (Android), відправляє на бекенд, бекенд верифікує через Apple App Store Server API / Google Play Developer API, зберігає статус у БД, віддає відповідь клієнту. RevenueCat робить це за вас — але якщо у вас кастомний бекенд, потрібно реалізувати самостійно.
Webhook-и важливіші, ніж здається. Користувач може скасувати підписку через налаштування телефону, не через додаток — додаток не отримає про цю подію в реальному часі. Тільки webhook від Apple/Google (або RevenueCat) дозволяє своєчасно оновити статус. Ми використовуємо перевірку підпису вхідних запитів через Apple's signedPayload та Google's DeveloperNotification.
Як RevenueCat спрощує інтеграцію?
Підтримувати StoreKit 2 та Google Billing одночасно, з урахуванням промо-кодів, оферт, відновлення покупок та серверної верифікації — це кілька місяців розробки. RevenueCat закриває більшу частину цього шару.
RevenueCat — не просто SDK для платежів. Це:
- Єдиний API для iOS та Android (і Stripe для вебу)
- Серверна верифікація та зберігання статусів підписок
- Webhooks на події (покупка, відновлення, скасування, billing issue)
- Аналітика по когортам, MRR, churn
- A/B тестування оферт через Experiments
Purchases.configure(withAPIKey:) при старті, Purchases.shared.getCustomerInfo() для отримання поточних entitlements — мінімальний інтегрований шар. Purchases.shared.purchase(package:) замість прямого виклику StoreKit/Billing.
У документації RevenueCat сказано: «RevenueCat handles receipt validation on the server side, reducing client-side complexity and preventing fraudulent purchases.»
Обмеження RevenueCat: платний (безкоштовно до певного рівня MRR, далі відсоток від доходу), не підходить для дуже складних flow з кількома storefront-ами або кастомними bundle-ами. Однак для типового SaaS-додатку економія на власній розробці є значною — інтеграція окупається швидко.
Stripe в мобільних додатках
Stripe — для оплати фізичних товарів, послуг, B2B-платежів де IAP не вимагається політикою платформи.
Stripe iOS SDK та Android SDK — PaymentSheet для готового UI оплати, PaymentSheetFlowController для кастомного UI зі збереженими картками. Payment Intent створюється на сервері, client secret передається в додаток — карткові дані ніколи не проходять через ваш сервер, лише через Stripe.
Apple Pay та Google Pay через Stripe: PKPaymentRequest (iOS) та GooglePayLauncher (Android) вже інтегровані в Stripe SDK. Конверсія у Apple Pay дає в 1.3–2 рази вищий результат, ніж форми з ручним введенням картки — це цифри, які ми підтвердили на десятці проєктів.
Збережені картки через SetupIntent + Customer API — користувач платить в один тап при повторному візиті. Compliance: PCI DSS SAQ A — найлегший рівень compliance, оскільки Stripe Tokenization прибирає потребу зберігати карткові дані на своїй стороні. Згідно з PCI DSS, передача токенів звільняє від необхідності сертифікації рівня 1.
3DS2 (Strong Customer Authentication) — обов'язковий для платежів у ЄС за PSD2. Stripe обробляє автоматично через PaymentIntent.confirmPayment, але потрібно коректно обробити .requiresAction статус та повернути користувача на потрібний екран після аутентифікації.
Що входить в роботу (deliverables)
| Документація/Артефакт |
Зміст |
| Архітектурна схема біллінгу |
Діаграма потоків: клієнт → SDK → сервер → store/webhook |
| Інтеграція SDK |
Підключення та конфігурація StoreKit 2, Google Billing, RevenueCat або Stripe |
| Серверна верифікація |
Реалізація ендпоінтів та обробка webhook (Apple/Google/RevenueCat) |
| Тестовий стенд |
Sandbox Apple, License Testers Google, Stripe Test Mode |
| Документація по запуску |
Опис ключів, provisioning profiles, TestFlight |
| Навчання команди |
Сесія з підтримки платіжного модуля |
Процес та терміни
Починаємо з з'ясування бізнес-моделі: підписки, разові покупки, consumables, freemium. Від цього залежить архітектура. Тестування IAP вимагає Sandbox-акаунтів (Apple) та License Testers (Google) — це окреме налаштування середовища.
Sandbox Apple поводиться не так, як production: підписки відновлюються кожні 5 хвилин замість місяця, inGracePeriod працює по-іншому. Обов'язково тестувати сценарії: закінчення тріалу, скасування, billing retry, refund.
Як налаштувати тестове середовище для платежів?
| Сценарій |
Інструмент |
Час реалізації |
| Підписки iOS + Android |
StoreKit 2 + Google Billing + RevenueCat |
2–3 тижні |
| Підписки з кастомним бекендом |
StoreKit 2 + Google Billing + власний webhook |
4–6 тижнів |
| Оплата карткою (фізичні товари) |
Stripe PaymentSheet |
1–2 тижні |
| Apple Pay / Google Pay |
Stripe або нативні SDK |
+ 3–5 днів |
| Повний платіжний стек |
Все вищеперераховане |
6–10 тижнів |
Розгорнути типові помилки при інтеграції
- Забули викликати
acknowledgePurchase() на Android — гроші повертаються через 3 дні.
- Не обробили
inGracePeriod — лояльні користувачі блокуються без доступу.
- Поклалися лише на Pushtokens при відновленні підписок — пропускаєте state оновлення.
- Використовували production-ключі в TestFlight — спрацьовують реальні списання.
Вартість розраховується індивідуально виходячи з набору інструментів та складності серверної логіки. У середньому ми вкладаємося в бюджет, який обговорюється окремо, але економія від запобігання помилок та churn окупає ці вкладення за 2–3 місяці.
Отримайте консультацію по вашому проєкту — зв'яжіться з нами. Ми допоможемо обрати оптимальну архітектуру платежів, яка пройде рев'ю сторів та не зламається при пікових навантаженнях.