431 lines
19 KiB
JavaScript
431 lines
19 KiB
JavaScript
// 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());
|
||
}
|