1
0
forked from BRT/arc
arc/scripts/ozon/pay_by_phone.js

431 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ozon/pay_by_phone.js
//
// JS-порт Ozon::PayByPhoneScript. Логика 1:1 с C++ (см.
// android/ozon/PayByPhoneScript.cpp).
//
// Контракт:
// params.account — {id, materialId, appCode, deviceId, lastNumbers, currency}
// params.phone — телефон получателя
// params.bankName — название банка получателя (для "Другой банк")
// params.amount — сумма перевода
function run(host) {
var account = host.params.account;
var phone = host.params.phone;
var bankName = host.params.bankName;
var amount = host.params.amount;
var device = host.dao.deviceGetById(account.deviceId);
var app = host.dao.bankProfileGet(account.deviceId, account.appCode);
if (!device || !device.id || !app || app.id < 0) {
var msg = "Device or app not found: " + account.deviceId;
host.log.error(msg);
return msg;
}
var adbId = device.adbSerial;
var counter = 0;
host.dump.beginScope("ozon", "CREATE_TRANSACTION_PHONE", adbId, {
materialId: account.materialId,
amount: amount,
phone: phone,
});
try {
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(adbId, ++counter));
var adNode = host.parser.tryToFindAdBanner();
if (!adNode.isEmpty) {
host.adb.makeTap(adbId, adNode.x, adNode.y);
host.timer.sleep(1000);
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(adbId, ++counter));
}
if (!host.parser.isHomeScreen()) {
if (!host.helpers.runAppAndGoToHomeScreen(adbId, app.package, app.pinCode,
device.screenWidth, device.screenHeight)) {
var err = "Cannot reach home screen: " + device.name + " (" + device.id + ")";
host.adb.tryToKillApplication(adbId, app.package);
return err;
}
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(adbId, ++counter));
}
host.helpers.tryToCloseAllBannersOnHomePage(adbId, device.screenWidth, device.screenHeight);
host.timer.sleep(500);
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(adbId, ++counter));
var gp = host.parser.tryToFindGooglePlayBanner(device.screenWidth, device.screenHeight);
if (!gp.isEmpty) {
host.adb.makeTap(adbId, gp.x, gp.y);
host.timer.sleep(1500);
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(adbId, ++counter));
}
var orderCard = host.parser.findTextNode("Заказать карту");
if (!orderCard.isEmpty) {
host.adb.makeTap(adbId, Math.floor(device.screenWidth / 2),
Math.floor(device.screenHeight / 20));
host.timer.sleep(1000);
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(adbId, ++counter));
}
var result = makePayment(host, adbId, app.package, app.pinCode, app.phone,
device.screenWidth, device.screenHeight,
account, phone, bankName, amount, counter);
host.dump.endScope(result || "");
return result;
} catch (e) {
host.dump.endScope(String(e));
throw e;
}
}
function makePayment(host, deviceId, packageName, pinCode, bankProfilePhone,
width, height, account, phone, bankName, amount, counter) {
function checkApp() {
if (!host.helpers.isAppRunning(deviceId, packageName)) {
return "Приложение было закрыто";
}
return null;
}
// 1. "Перевести"
var transferBtn = host.parser.findButtonNode("Перевести", false);
if (transferBtn.isEmpty) return "Кнопка 'Перевести' не найдена на домашнем экране";
host.adb.makeTap(deviceId, transferBtn.x, transferBtn.y);
host.timer.sleep(2000);
// 2. Ждём экран "Платежи и переводы" с первым EditText
var paymentsPageLoaded = false;
for (var i = 0; i < 10; ++i) {
var e = checkApp(); if (e) return e;
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
host.helpers.closeGooglePlayBannerIfVisible(deviceId, width, height);
if (!host.parser.findFirstEditText().isEmpty) { paymentsPageLoaded = true; break; }
host.timer.sleep(1500);
}
if (!paymentsPageLoaded) return "Экран 'Платежи и переводы' не загрузился";
// 3. Тапаем "Телефон получателя"
var phoneFieldTap = host.parser.findFirstEditText();
host.adb.makeTap(deviceId, phoneFieldTap.x, phoneFieldTap.y);
host.timer.sleep(2000);
// 4. Ждём "Кому перевести"
var phoneScreenLoaded = false;
for (var j = 0; j < 10; ++j) {
var e2 = checkApp(); if (e2) return e2;
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
host.helpers.closeGooglePlayBannerIfVisible(deviceId, width, height);
var notNow = host.parser.findButtonNode("Не сейчас", false);
if (!notNow.isEmpty) {
host.adb.makeTap(deviceId, notNow.x, notNow.y);
host.timer.sleep(1500);
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
}
if (!host.parser.findTextNode("Кому перевести").isEmpty) { phoneScreenLoaded = true; break; }
host.timer.sleep(1500);
}
if (!phoneScreenLoaded) return "Экран 'Кому перевести' не загрузился";
// 5. Вводим телефон
var phoneInput = {isEmpty: true};
for (var k = 0; k < 10; ++k) {
var e3 = checkApp(); if (e3) return e3;
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
host.helpers.closeGooglePlayBannerIfVisible(deviceId, width, height);
phoneInput = host.parser.findFirstEditText();
if (!phoneInput.isEmpty) break;
host.timer.sleep(1500);
}
if (phoneInput.isEmpty) return "Поле ввода телефона не найдено";
host.adb.makeTap(deviceId, phoneInput.x, phoneInput.y);
host.timer.sleep(500);
host.adb.inputText(deviceId, phone);
host.timer.sleep(2000);
// 6. Ждём экран "По номеру телефона" с обработкой выбора банка / последних
var sumScreenLoaded = false;
var bankSelected = false;
var otherBankClicked = false;
var freezePrev = "", freezeStreak = 0;
var phoneDigits = phone.replace(/[^0-9]/g, '');
for (var attempt = 0; attempt < 20; ++attempt) {
var e4 = checkApp(); if (e4) return e4;
var xml = host.adb.getScreenDumpAsXml(deviceId, ++counter);
host.parser.parseAndSaveXml(xml);
host.helpers.closeGooglePlayBannerIfVisible(deviceId, width, height);
if (host.helpers.isUsefulDump(xml)) {
if (freezePrev && xml === freezePrev) {
if (++freezeStreak >= 3) { host.log.warn("screen frozen"); break; }
} else freezeStreak = 0;
freezePrev = xml;
}
// Уже на экране суммы?
var sumTitle = host.parser.findTextNode("По номеру телефона");
if (!sumTitle.isEmpty && sumTitle.y < height / 7) {
sumScreenLoaded = true;
break;
}
// Кнопка с этим телефоном (новый перевод / последние)
var foundPhoneBtn = false;
var allBtns = host.parser.findAllButtons();
for (var bi = 0; bi < allBtns.length; ++bi) {
var btn = allBtns[bi];
var btnDigits = (btn.text || "").replace(/[^0-9]/g, '');
if (btnDigits.indexOf(phoneDigits) !== -1
|| (btnDigits.length >= 10 && phoneDigits.indexOf(btnDigits.slice(-10)) !== -1)) {
host.log.debug("Found button with phone: " + btn.text);
host.adb.makeTap(deviceId, btn.x, btn.y);
host.timer.sleep(2000);
foundPhoneBtn = true;
break;
}
}
if (foundPhoneBtn) continue;
// Экран выбора банка
if (!bankSelected) {
// Вариант 1: "Другой банк" (content-desc="other-bank-item")
var foundOtherBank = false;
var allNodes = host.parser.findAllNodes();
for (var ai = 0; ai < allNodes.length; ++ai) {
var n = allNodes[ai];
if ((n.contentDesc || "").indexOf("other-bank-item") !== -1) {
host.adb.makeTap(deviceId, n.x, n.y);
host.timer.sleep(2000);
foundOtherBank = true;
otherBankClicked = true;
break;
}
}
if (foundOtherBank) continue;
// Вариант 2: после клика на "Другой банк" — поиск + выбор
if (otherBankClicked) {
var bankInput = host.parser.findFirstEditText();
if (!bankInput.isEmpty) {
// Переключаем IME до тапа — иначе layout сдвинется
if (!host.adb.enableAdbIMEKeyboard(deviceId)) {
return "Не удалось активировать ADB Keyboard (см. логи 'ime list -s')";
}
host.timer.sleep(800);
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
var freshBankInput = host.parser.findFirstEditText();
if (!freshBankInput.isEmpty) bankInput = freshBankInput;
host.adb.makeTap(deviceId, bankInput.x, bankInput.y);
host.timer.sleep(500);
host.adb.inputAdbIMEText(deviceId, bankName);
host.timer.sleep(2500);
// Верификация
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
var verify = host.parser.findFirstEditText();
if (!verify.text) {
host.log.warn("Поле банка пустое — ретрай");
host.adb.makeTap(deviceId, bankInput.x, bankInput.y);
host.timer.sleep(500);
host.adb.inputAdbIMEText(deviceId, bankName);
host.timer.sleep(2500);
}
host.adb.disableAdbIMEKeyboard(deviceId);
host.timer.sleep(2000);
// Ищем банк в списке (Button с непустым content-desc, не служебный)
var bankFound = false;
for (var ba = 0; ba < 10; ++ba) {
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
var searchField = host.parser.findFirstEditText();
if (!searchField.text || !(searchField.text || "").trim()) {
host.timer.sleep(1500);
continue;
}
var all2 = host.parser.findAllNodes();
for (var ai2 = 0; ai2 < all2.length; ++ai2) {
var node2 = all2[ai2];
if (node2.className === "android.widget.Button"
&& node2.contentDesc
&& node2.contentDesc !== "base-go-back-button"
&& node2.contentDesc !== "native-toolbar-close-button"
&& node2.contentDesc !== "clear-input-icon") {
var tapX = node2.x1 + Math.floor(node2.width / 4);
host.log.debug("Found bank: " + node2.contentDesc + " tapX=" + tapX);
host.adb.makeTap(deviceId, tapX, node2.y);
bankFound = true;
break;
}
}
if (bankFound) break;
host.timer.sleep(1500);
}
if (!bankFound) return "Банк '" + bankName + "' не найден в списке";
bankSelected = true;
host.timer.sleep(2000);
continue;
}
}
}
host.timer.sleep(1500);
}
if (!sumScreenLoaded) return "Экран ввода суммы не загрузился";
// 7. Вводим сумму — берём верхний (минимальный y1) EditText с input___
var sumInput = {isEmpty: true};
for (var sa = 0; sa < 10; ++sa) {
var e5 = checkApp(); if (e5) return e5;
var candidates = [];
var allNodes2 = host.parser.findAllNodes();
for (var ci = 0; ci < allNodes2.length; ++ci) {
var nn = allNodes2[ci];
if (nn.className !== "android.widget.EditText") continue;
if (nn.height <= 0 || nn.height > height * 4 / 5) continue;
if (String(nn.resourceId || "").indexOf("input___") !== 0) continue;
candidates.push(nn);
}
if (candidates.length > 0) {
candidates.sort(function(a, b) { return a.y1 - b.y1; });
sumInput = candidates[0]; // верхний = amount
break;
}
sumInput = host.parser.findFirstEditText();
if (!sumInput.isEmpty) break;
host.timer.sleep(1500);
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
host.helpers.closeGooglePlayBannerIfVisible(deviceId, width, height);
}
if (sumInput.isEmpty) return "Поле ввода суммы не найдено";
host.adb.makeTap(deviceId, sumInput.x, sumInput.y);
host.timer.sleep(500);
host.adb.inputText(deviceId, String(Math.floor(amount)));
host.timer.sleep(1000);
host.adb.hideKeyboard(deviceId);
host.timer.sleep(800);
// 8. "Продолжить"
var continueBtn = {isEmpty: true};
for (var ca = 0; ca < 5; ++ca) {
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
continueBtn = host.parser.findButtonNode("Продолжить", false);
if (!continueBtn.isEmpty && continueBtn.height > 0) break;
host.adb.hideKeyboard(deviceId);
host.timer.sleep(1000);
}
if (continueBtn.isEmpty || continueBtn.height === 0) {
return "Кнопка 'Продолжить' не найдена или перекрыта клавиатурой";
}
host.adb.makeTap(deviceId, continueBtn.x, continueBtn.y);
host.timer.sleep(2000);
// 9. Ждём "Подтвердите перевод"
var confirmLoaded = false;
for (var cf = 0; cf < 10; ++cf) {
var e6 = checkApp(); if (e6) return e6;
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
host.helpers.closeGooglePlayBannerIfVisible(deviceId, width, height);
if (!host.parser.findTextNode("Подтвердите перевод").isEmpty) { confirmLoaded = true; break; }
host.timer.sleep(1500);
}
if (!confirmLoaded) return "Экран подтверждения не загрузился";
// 10. Тапаем "Перевести ..."
var transferConfirmBtn = {isEmpty: true};
for (var tc = 0; tc < 10; ++tc) {
var e7 = checkApp(); if (e7) return e7;
transferConfirmBtn = host.parser.findButtonNode("Перевести", true);
if (!transferConfirmBtn.isEmpty) break;
host.timer.sleep(1500);
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
host.helpers.closeGooglePlayBannerIfVisible(deviceId, width, height);
}
if (transferConfirmBtn.isEmpty) return "Кнопка 'Перевести ...' не найдена на экране подтверждения";
host.adb.makeTap(deviceId, transferConfirmBtn.x, transferConfirmBtn.y);
host.timer.sleep(3000);
// PIN после confirm
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
var pinNodes = host.parser.tryToFindPinCode(pinCode, "Введите ПИН");
if (pinNodes && pinNodes.length > 0) {
host.helpers.inputPinCode(deviceId, pinNodes);
host.timer.sleep(3000);
}
// 11. Ждём результат
for (var w = 0; w < 15; ++w) {
var e8 = checkApp(); if (e8) return e8;
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
host.helpers.closeGooglePlayBannerIfVisible(deviceId, width, height);
var pinLater = host.parser.tryToFindPinCode(pinCode, "Введите ПИН");
if (pinLater && pinLater.length > 0) {
host.helpers.inputPinCode(deviceId, pinLater);
host.timer.sleep(3000);
continue;
}
if (!host.parser.findTextNode("Успешно").isEmpty) {
var tx = {
accountId: account.id,
amount: -amount,
phone: phone,
bankName: "ozon",
type: "phone",
status: "complete",
timestamp: new Date().toISOString(),
completeTime: new Date().toISOString(),
bankTime: formatBankTime(new Date()),
};
var txId = host.dao.transactionInsert(tx);
host.log.debug("Saved tx to DB, id: " + txId);
if (txId !== -1) {
host.helpers.saveTransactionReceipt(deviceId, txId);
var externalId = "ozon_" + bankProfilePhone + "_" + Math.floor(Date.now() / 1000);
host.dao.transactionUpdateBankTrExternalId(txId, externalId);
}
host.adb.goBack(deviceId);
host.timer.sleep(1000);
host.adb.goBack(deviceId);
host.timer.sleep(1000);
return "";
}
if (!host.parser.findTextNode("Не удалось").isEmpty) {
host.adb.goBack(deviceId);
host.timer.sleep(1000);
host.adb.goBack(deviceId);
host.timer.sleep(1000);
return "Перевод не удался (экран 'Не удалось')";
}
host.timer.sleep(2000);
}
return "Не дождались результата перевода";
}
function formatBankTime(d) {
var utc = d.getTime();
var msk = new Date(utc + 3 * 3600 * 1000);
function pad(n) { return n < 10 ? '0' + n : '' + n; }
return pad(msk.getUTCDate()) + "." + pad(msk.getUTCMonth() + 1) + "." + msk.getUTCFullYear()
+ ", " + pad(msk.getUTCHours()) + ":" + pad(msk.getUTCMinutes());
}