Я працюю в невеликому ізраїльському стартапі, наш продукт - платформа для замовлення їжі з ресторанів, кафе і магазинів. На відміну від десятків подібних сервісів, ми монополісти на студентському ринку в США. Ми обробляємо на піку близько сотні тисяч замовлень на день і один з платіжних шлюзів у продакшні побудований на автоматизації GUI для Win32 програми за допомогою бібліотеки pywinauto.
По-перше, чому ми працюємо тільки в університетах? Багато ідей часто добре виглядають тільки в перспективі. Наприклад, та ж ідея доставки їжі. Здається, здорово написати додаток, щоб можна було замовити з будь-якого ресторану, не паритися з оплатою тощо. Всі, хто намагається цю ідею реалізувати, натикаються на так звану Chicken or the egg problem. Поки в додатку мало ресторанів, він безглуздий; а поки користувачів небагато, ресторани не поспішають інтегрувати новий додаток у свій робочий процес. Зазвичай на цьому моменті все вмирає. Ця ситуація не унікальна для програми для замовлення їжі.
Вирішенням подібної проблеми зазвичай буває якась гібридна модель, коли, крім прийдешньої користі від соціалізації, є користь для кожного окремого юзера. Наприклад, Waze. Зараз це додаток, який знає про всі затори, аварії і поліції на дорозі, але починали вони як безкоштовний GPS-додаток, від якого користь була кожному. Бувають й інші рішення, про одне з них розповім на нашому прикладі.
Упершись у цю проблему, нас чекала та ж доля. Ми досить безуспішно намагалися пробитися на ринку Сан-Франциско, де повно подібних рішень, і, перш ніж тихо померти, вирішили спробувати ще одну ідею. Вона полягала в тому, щоб не намагатися домовитися з усіма ресторанами, а тільки з якимось майданчиком, у якого є важелі тиску на локальні бізнеси.
Вибір припав на університети. Пропонуючи відсоток від доходу і сервіс для студентів, ми були відмінною пропозицією для університету, проблеми локальних ресторанів, пов'язані з інтеграцією нового додатку в свій бізнес процес, їх не цікавили, для них ми були прямою і непрямою вигодою. Вони зобов'язували бізнес на території кампусу працювати з нашим додатком. Ми інтегрувалися в усі ресторани на території університету - для студентів наш додаток ставав дуже зручним: можна замовляти обід під час лекцій і не чекати свого замовлення на зміні. Ми моментально захоплюємо більшу частину кампусу, причому вірусний ефект дуже великий. Коли людина сиротливо чекає в черзі, а поруч приходять люди, беруть своє готове замовлення і відразу йдуть, найчастіше наш додаток скачують прямо на місці.
У якийсь момент у нас виникла проблема. Справа в тому, що в Америці дуже непросто отримати кредитну картку, і у більшості студентів її немає. З іншого боку студентські посвідчення видаються у вигляді пластикових карток з магнітною смугою і є інструментом оплати всередині кампусу. Університет нараховує на них щось на зразок стипендій. Ці кошти більш структуровані: там є окремі частини, які можна витрачати вільно, а є кошти тільки на їжу. І ці карти приймають тільки на території кампусу.
Ми, як компанія, яка працює зі студентами, звичайно ж хотіли обслуговувати ці картки. Але не все так просто. Компанія, що випускає ці картки - це не банк. Ринок обслуговування студентських карт практично монополізований і розділений між трьома компаніями на сотні університетів по всій Америці.
Одна з цих компаній намагалася запустити сервіс, подібний нашому, і, програвши конкуренцію, в кращих традиціях пісочних майданчиків «образилася» і відмовилася вести з нами будь-яку співпрацю. Справа в тому, що за договором з університетом, бізнесом, який має право приймати оплату через студентські картки, компанія-емітент зобов'язана надати термінал з десктопним додатком, куди можна ввести номер студентської картки і зняти гроші. Це дуже зручно для всяких кіосків і сосискових. А ось API масового обслуговування за договором надавати не обов'язково, і відкривати вони його нам не захотіли. Довелося працювати з тим, що є. У підсумку ми знайшли досить незвичайне рішення для платіжного шлюзу, яке за перші кілька місяців свого існування досить успішно працює в 6 університетах: проведено десятки тисяч транзакцій на сотні тисяч доларів, а наступного навчального року це рішення буде масштабовано на кілька десятків американських університетів з обігом коштів у десятки мільйонів доларів.
Як це працює?
Про наше рішення. Повинен зауважити, що нам дуже важливо було знайти рішення, яке абсолютно легальне. Наприклад, Reverse Enginering - це нелегально, снифити трафік і симулювати фронтенд цього касового терміналу теж може бути проблематично, а ось автоматизувати кліки за десктопним додатком - абсолютно легально. У кампусі нам виділяється віртуальна машина на Windows з встановленим десктопним касовим додатком. Там само встановлюється зв'язка Flask + Tornado. Коли користувач з телефону оформляє замовлення, на віртуальну машину приходить запит з усіма необхідними параметрами: сума, номер студентської картки тощо. Далі за допомогою pywinauto ми вводимо суму, номер картки, всі необхідні параметри (там досить складна логіка в плані знижок, безкоштовних обідів у певну частину дня тощо). Проводимо транзакцію, перевіряємо результат і повертаємо відповідь на сервер. На обробку однієї транзакції спочатку йшло близько 20 секунд, але в підсумку вдалося скоротити до 3.
Під час профілювання з'ясувалися деякі особливості бібліотеки.
Виклики Application () .Connect () для підключення до програми мають багато різних параметрів і, наприклад, ідентифікація програми або вікна за class_name працює в 20 разів швидше, ніж за title_re (регулярний вираз заголовка вікна).
Другим несподіваним моментом виявилося, що якщо вікно ще не відкрилося, то виклик Connect () бере багато часу (до декількох секунд), перш ніж кинути виняток. Його не варто викликати не будучи впевненим, що вікно вже відкрите, краще знайти евристичну модель, яка дозволить зрозуміти, що виклик буде успішним. У моєму випадку при відкритті нового вікна форма головного програми припиняла бути'active', це можна було відловити з допомогою виклику form. WaitNot ('active'), після повернення якого можна сміливо викликати Connect ().
Ще одна з проблем, з якою ми зіткнулися: автоматизація через Win32 API не працює без відкритої сесії користувача на машині. Це загальна проблема для всіх таких інструментів. Наприклад, у бібліотеці pywinauto не працюють методи ClickInput і TypeKeys, якщо сесія залочена або RDP підключення згорнуто. Це було моє перше в житті знайомство з автоматизацією GUI і Win32 API, так що рішення проблеми могло сильно затягнутися. На щастя, у бібліотеки чудовий мейнтейнер vasily-v-ryabov. Висловлюю йому величезну подяку за докладні консультації.
Ми знайшли рішення всім проблемам, в основному багато питань вирішували альтернативними викликами. Наприклад, коли зустрівся нестандартний віджет перемикання табів у додатку, який не реагував на жоден стандартний виклик, рішення знайшлося шляхом аналізу Windows Messages в Spy++ і відправки тих же повідомлень за допомогою методу PostMessage в pywinauto.
Бібліотека активно розвивається і, як показала практика, досить стабільна навіть для того, щоб використовувати її в продакшні для реалізації платіжного шлюзу. Майже всі проблеми, пов'язані з урізаними функціоналом за відсутності сесії, будуть вирішені в наступному релізі (0.6.0), який готується до виходу в цьому році (гілка UIA на гітхабі). Наприклад, метод send_chars дозволить успішно передавати майже будь-які комбінації символів у неактивне або навіть згорнуте вікно. І найголовніше, в гілці UIA є підтримка технології Microsoft UI Automation (для WinCes, WPF, StoreApps, Qt і браузерів), але це вже тема окремої статті.
Якщо вам здалося, що реалізація платіжного шлюзу шляхом автоматизації GUI - це не верх сюрреалізму, то можу додати, що один з університетів Техасу має дуже жорсткі обмеження щодо доступу до внутрішньої мережі, так що деплою, дебаг і апгрейд модуля, який обробляє платежі десятків тисяч людей відбувається по скайпу - «А тепер напиши git fetch» - і все в такому дусі. Між іншим аптайм у нас > 99.6%. Спасибі pywinauto).