payments_checkout_process.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "payments/payments_checkout_process.h"
  8. #include "payments/payments_form.h"
  9. #include "payments/ui/payments_panel.h"
  10. #include "main/main_session.h"
  11. #include "main/main_account.h"
  12. #include "storage/storage_account.h"
  13. #include "history/history_item.h"
  14. #include "history/history.h"
  15. #include "data/data_user.h" // UserData::isBot.
  16. #include "boxes/passcode_box.h"
  17. #include "core/local_url_handlers.h" // TryConvertUrlToLocal.
  18. #include "core/file_utilities.h" // File::OpenUrl.
  19. #include "core/core_cloud_password.h" // Core::CloudPasswordState
  20. #include "core/click_handler_types.h"
  21. #include "lang/lang_keys.h"
  22. #include "apiwrap.h"
  23. #include "api/api_cloud_password.h"
  24. #include "window/themes/window_theme.h"
  25. #include <QJsonDocument>
  26. #include <QJsonObject>
  27. #include <QJsonArray>
  28. #include <QJsonValue>
  29. namespace Payments {
  30. namespace {
  31. struct SessionProcesses {
  32. base::flat_map<FullMsgId, std::unique_ptr<CheckoutProcess>> byItem;
  33. base::flat_map<QString, std::unique_ptr<CheckoutProcess>> bySlug;
  34. base::flat_map<uint64, std::unique_ptr<CheckoutProcess>> byRandomId;
  35. base::flat_map<FullMsgId, PaidInvoice> paymentStartedByItem;
  36. base::flat_map<QString, PaidInvoice> paymentStartedBySlug;
  37. rpl::lifetime lifetime;
  38. };
  39. base::flat_map<not_null<Main::Session*>, SessionProcesses> Processes;
  40. [[nodiscard]] SessionProcesses &LookupSessionProcesses(
  41. not_null<Main::Session*> session) {
  42. const auto i = Processes.find(session);
  43. if (i != end(Processes)) {
  44. return i->second;
  45. }
  46. const auto j = Processes.emplace(session).first;
  47. auto &result = j->second;
  48. session->account().sessionChanges(
  49. ) | rpl::start_with_next([=] {
  50. Processes.erase(session);
  51. }, result.lifetime);
  52. return result;
  53. }
  54. } // namespace
  55. void CheckoutProcess::Start(
  56. not_null<const HistoryItem*> item,
  57. Mode mode,
  58. Fn<void(CheckoutResult)> reactivate,
  59. Fn<void(NonPanelPaymentForm)> nonPanelPaymentFormProcess) {
  60. auto &processes = LookupSessionProcesses(&item->history()->session());
  61. const auto media = item->media();
  62. const auto invoice = media ? media->invoice() : nullptr;
  63. if (mode == Mode::Payment && !invoice) {
  64. return;
  65. }
  66. const auto id = (invoice && invoice->receiptMsgId)
  67. ? FullMsgId(item->history()->peer->id, invoice->receiptMsgId)
  68. : item->fullId();
  69. if (invoice) {
  70. mode = invoice->receiptMsgId ? Mode::Receipt : Mode::Payment;
  71. } else if (mode == Mode::Payment) {
  72. LOG(("API Error: CheckoutProcess Payment start without invoice."));
  73. return;
  74. }
  75. const auto i = processes.byItem.find(id);
  76. if (i != end(processes.byItem)) {
  77. i->second->setReactivateCallback(std::move(reactivate));
  78. i->second->setNonPanelPaymentFormProcess(
  79. std::move(nonPanelPaymentFormProcess));
  80. i->second->requestActivate();
  81. return;
  82. }
  83. const auto j = processes.byItem.emplace(
  84. id,
  85. std::make_unique<CheckoutProcess>(
  86. InvoiceId{ InvoiceMessage{ item->history()->peer, id.msg } },
  87. mode,
  88. std::move(reactivate),
  89. std::move(nonPanelPaymentFormProcess),
  90. PrivateTag{})).first;
  91. j->second->requestActivate();
  92. }
  93. void CheckoutProcess::Start(
  94. not_null<Main::Session*> session,
  95. const QString &slug,
  96. Fn<void(CheckoutResult)> reactivate,
  97. Fn<void(NonPanelPaymentForm)> nonPanelPaymentFormProcess) {
  98. auto &processes = LookupSessionProcesses(session);
  99. const auto i = processes.bySlug.find(slug);
  100. if (i != end(processes.bySlug)) {
  101. i->second->setReactivateCallback(std::move(reactivate));
  102. i->second->setNonPanelPaymentFormProcess(
  103. std::move(nonPanelPaymentFormProcess));
  104. i->second->requestActivate();
  105. return;
  106. }
  107. const auto j = processes.bySlug.emplace(
  108. slug,
  109. std::make_unique<CheckoutProcess>(
  110. InvoiceId{ InvoiceSlug{ session, slug } },
  111. Mode::Payment,
  112. std::move(reactivate),
  113. std::move(nonPanelPaymentFormProcess),
  114. PrivateTag{})).first;
  115. j->second->requestActivate();
  116. }
  117. void CheckoutProcess::Start(
  118. InvoicePremiumGiftCode giftCodeInvoice,
  119. Fn<void(CheckoutResult)> reactivate,
  120. Fn<void(NonPanelPaymentForm)> nonPanelPaymentFormProcess) {
  121. const auto randomId = giftCodeInvoice.randomId;
  122. auto id = InvoiceId{ std::move(giftCodeInvoice) };
  123. auto &processes = LookupSessionProcesses(SessionFromId(id));
  124. const auto i = processes.byRandomId.find(randomId);
  125. if (i != end(processes.byRandomId)) {
  126. i->second->setReactivateCallback(std::move(reactivate));
  127. i->second->setNonPanelPaymentFormProcess(
  128. std::move(nonPanelPaymentFormProcess));
  129. i->second->requestActivate();
  130. return;
  131. }
  132. const auto j = processes.byRandomId.emplace(
  133. randomId,
  134. std::make_unique<CheckoutProcess>(
  135. std::move(id),
  136. Mode::Payment,
  137. std::move(reactivate),
  138. std::move(nonPanelPaymentFormProcess),
  139. PrivateTag{})).first;
  140. j->second->requestActivate();
  141. }
  142. void CheckoutProcess::Start(
  143. InvoiceCredits creditsInvoice,
  144. Fn<void(CheckoutResult)> reactivate) {
  145. const auto randomId = creditsInvoice.randomId;
  146. auto id = InvoiceId{ std::move(creditsInvoice) };
  147. auto &processes = LookupSessionProcesses(SessionFromId(id));
  148. const auto i = processes.byRandomId.find(randomId);
  149. if (i != end(processes.byRandomId)) {
  150. i->second->setReactivateCallback(std::move(reactivate));
  151. i->second->requestActivate();
  152. return;
  153. }
  154. const auto j = processes.byRandomId.emplace(
  155. randomId,
  156. std::make_unique<CheckoutProcess>(
  157. std::move(id),
  158. Mode::Payment,
  159. std::move(reactivate),
  160. nullptr,
  161. PrivateTag{})).first;
  162. j->second->requestActivate();
  163. }
  164. void CheckoutProcess::Start(
  165. InvoiceStarGift giftInvoice,
  166. Fn<void(CheckoutResult)> reactivate,
  167. Fn<void(NonPanelPaymentForm)> nonPanelPaymentFormProcess) {
  168. const auto randomId = giftInvoice.randomId;
  169. auto id = InvoiceId{ std::move(giftInvoice) };
  170. auto &processes = LookupSessionProcesses(SessionFromId(id));
  171. const auto i = processes.byRandomId.find(randomId);
  172. if (i != end(processes.byRandomId)) {
  173. i->second->setReactivateCallback(std::move(reactivate));
  174. i->second->setNonPanelPaymentFormProcess(
  175. std::move(nonPanelPaymentFormProcess));
  176. return;
  177. }
  178. processes.byRandomId.emplace(
  179. randomId,
  180. std::make_unique<CheckoutProcess>(
  181. std::move(id),
  182. Mode::Payment,
  183. std::move(reactivate),
  184. std::move(nonPanelPaymentFormProcess),
  185. PrivateTag{}));
  186. }
  187. std::optional<PaidInvoice> CheckoutProcess::InvoicePaid(
  188. not_null<const HistoryItem*> item) {
  189. const auto session = &item->history()->session();
  190. const auto itemId = item->fullId();
  191. const auto i = Processes.find(session);
  192. if (i == end(Processes)) {
  193. return std::nullopt;
  194. }
  195. const auto k = i->second.paymentStartedByItem.find(itemId);
  196. if (k == end(i->second.paymentStartedByItem)) {
  197. return std::nullopt;
  198. }
  199. const auto result = k->second;
  200. i->second.paymentStartedByItem.erase(k);
  201. const auto j = i->second.byItem.find(itemId);
  202. if (j != end(i->second.byItem)) {
  203. j->second->closeAndReactivate(CheckoutResult::Paid);
  204. } else if (i->second.paymentStartedByItem.empty()
  205. && i->second.byItem.empty()
  206. && i->second.paymentStartedBySlug.empty()
  207. && i->second.bySlug.empty()
  208. && i->second.byRandomId.empty()) {
  209. Processes.erase(i);
  210. }
  211. return result;
  212. }
  213. std::optional<PaidInvoice> CheckoutProcess::InvoicePaid(
  214. not_null<Main::Session*> session,
  215. const QString &slug) {
  216. const auto i = Processes.find(session);
  217. if (i == end(Processes)) {
  218. return std::nullopt;
  219. }
  220. const auto k = i->second.paymentStartedBySlug.find(slug);
  221. if (k == end(i->second.paymentStartedBySlug)) {
  222. return std::nullopt;
  223. }
  224. const auto result = k->second;
  225. i->second.paymentStartedBySlug.erase(k);
  226. const auto j = i->second.bySlug.find(slug);
  227. if (j != end(i->second.bySlug)) {
  228. j->second->closeAndReactivate(CheckoutResult::Paid);
  229. } else if (i->second.paymentStartedByItem.empty()
  230. && i->second.byItem.empty()
  231. && i->second.paymentStartedBySlug.empty()
  232. && i->second.bySlug.empty()
  233. && i->second.byRandomId.empty()) {
  234. Processes.erase(i);
  235. }
  236. return result;
  237. }
  238. void CheckoutProcess::ClearAll() {
  239. Processes.clear();
  240. }
  241. void CheckoutProcess::RegisterPaymentStart(
  242. not_null<CheckoutProcess*> process,
  243. PaidInvoice info) {
  244. const auto i = Processes.find(process->_session);
  245. Assert(i != end(Processes));
  246. for (const auto &[itemId, itemProcess] : i->second.byItem) {
  247. if (itemProcess.get() == process) {
  248. i->second.paymentStartedByItem.emplace(itemId, info);
  249. return;
  250. }
  251. }
  252. for (const auto &[slug, itemProcess] : i->second.bySlug) {
  253. if (itemProcess.get() == process) {
  254. i->second.paymentStartedBySlug.emplace(slug, info);
  255. return;
  256. }
  257. }
  258. for (const auto &[randomId, itemProcess] : i->second.byRandomId) {
  259. if (itemProcess.get() == process) {
  260. return;
  261. }
  262. }
  263. }
  264. void CheckoutProcess::UnregisterPaymentStart(
  265. not_null<CheckoutProcess*> process) {
  266. const auto i = Processes.find(process->_session);
  267. if (i == end(Processes)) {
  268. return;
  269. }
  270. for (const auto &[itemId, itemProcess] : i->second.byItem) {
  271. if (itemProcess.get() == process) {
  272. i->second.paymentStartedByItem.remove(itemId);
  273. break;
  274. }
  275. }
  276. for (const auto &[slug, itemProcess] : i->second.bySlug) {
  277. if (itemProcess.get() == process) {
  278. i->second.paymentStartedBySlug.remove(slug);
  279. break;
  280. }
  281. }
  282. for (const auto &[randomId, itemProcess] : i->second.byRandomId) {
  283. if (itemProcess.get() == process) {
  284. break;
  285. }
  286. }
  287. if (i->second.paymentStartedByItem.empty()
  288. && i->second.byItem.empty()
  289. && i->second.paymentStartedBySlug.empty()
  290. && i->second.bySlug.empty()
  291. && i->second.byRandomId.empty()) {
  292. Processes.erase(i);
  293. }
  294. }
  295. CheckoutProcess::CheckoutProcess(
  296. InvoiceId id,
  297. Mode mode,
  298. Fn<void(CheckoutResult)> reactivate,
  299. Fn<void(NonPanelPaymentForm)> nonPanelPaymentFormProcess,
  300. PrivateTag)
  301. : _session(SessionFromId(id))
  302. , _form(std::make_unique<Form>(id, (mode == Mode::Receipt)))
  303. , _panel(std::make_unique<Ui::Panel>(panelDelegate()))
  304. , _reactivate(std::move(reactivate))
  305. , _nonPanelPaymentFormProcess(std::move(nonPanelPaymentFormProcess)) {
  306. _form->updates(
  307. ) | rpl::start_with_next([=](const FormUpdate &update) {
  308. handleFormUpdate(update);
  309. }, _lifetime);
  310. _panel->savedMethodChosen(
  311. ) | rpl::start_with_next([=](QString id) {
  312. _form->chooseSavedMethod(id);
  313. }, _panel->lifetime());
  314. _panel->backRequests(
  315. ) | rpl::start_with_next([=] {
  316. panelCancelEdit();
  317. }, _panel->lifetime());
  318. if (!_nonPanelPaymentFormProcess) {
  319. showForm();
  320. }
  321. _panel->toggleProgress(true);
  322. if (mode == Mode::Payment) {
  323. _session->api().cloudPassword().state(
  324. ) | rpl::start_with_next([=](const Core::CloudPasswordState &state) {
  325. _form->setHasPassword(state.hasPassword);
  326. }, _lifetime);
  327. }
  328. }
  329. CheckoutProcess::~CheckoutProcess() {
  330. }
  331. void CheckoutProcess::setReactivateCallback(
  332. Fn<void(CheckoutResult)> reactivate) {
  333. _reactivate = std::move(reactivate);
  334. }
  335. void CheckoutProcess::setNonPanelPaymentFormProcess(
  336. Fn<void(NonPanelPaymentForm)> callback) {
  337. _nonPanelPaymentFormProcess = std::move(callback);
  338. }
  339. void CheckoutProcess::requestActivate() {
  340. if (!_nonPanelPaymentFormProcess) {
  341. _panel->requestActivate();
  342. }
  343. }
  344. not_null<Ui::PanelDelegate*> CheckoutProcess::panelDelegate() {
  345. return static_cast<PanelDelegate*>(this);
  346. }
  347. void CheckoutProcess::handleFormUpdate(const FormUpdate &update) {
  348. v::match(update, [&](const ToggleProgress &data) {
  349. _panel->toggleProgress(data.shown);
  350. }, [&](const FormReady &) {
  351. performInitialSilentValidation();
  352. if (!_initialSilentValidation) {
  353. showForm();
  354. }
  355. if (!_form->paymentMethod().savedCredentials.empty()) {
  356. _session->api().cloudPassword().reload();
  357. }
  358. }, [&](const ThumbnailUpdated &data) {
  359. _panel->updateFormThumbnail(data.thumbnail);
  360. }, [&](const ValidateFinished &) {
  361. if (_initialSilentValidation) {
  362. _initialSilentValidation = false;
  363. }
  364. showForm();
  365. const auto submitted = (_submitState == SubmitState::Validating);
  366. _submitState = SubmitState::Validated;
  367. if (submitted) {
  368. panelSubmit();
  369. }
  370. }, [&](const PaymentMethodUpdate &data) {
  371. showForm();
  372. if (data.requestNewPassword) {
  373. requestSetPassword();
  374. }
  375. }, [&](const TmpPasswordRequired &) {
  376. UnregisterPaymentStart(this);
  377. _submitState = SubmitState::Validated;
  378. requestPassword();
  379. }, [&](const BotTrustRequired &data) {
  380. UnregisterPaymentStart(this);
  381. _submitState = SubmitState::Validated;
  382. _panel->showWarning(data.bot->name(), data.provider->name());
  383. if (const auto box = _enterPasswordBox.data()) {
  384. box->closeBox();
  385. }
  386. }, [&](const VerificationNeeded &data) {
  387. auto bottomText = tr::lng_payments_processed_by(
  388. lt_provider,
  389. rpl::single(_form->invoice().provider));
  390. _sendFormFailed = false;
  391. _sendFormPending = true;
  392. if (!_panel->showWebview(data.url, false, std::move(bottomText))) {
  393. File::OpenUrl(data.url);
  394. close();
  395. }
  396. }, [&](const PaymentFinished &data) {
  397. const auto weak = base::make_weak(this);
  398. _session->api().applyUpdates(data.updates);
  399. if (weak) {
  400. closeAndReactivate(CheckoutResult::Paid);
  401. }
  402. }, [&](const CreditsPaymentStarted &data) {
  403. if (_nonPanelPaymentFormProcess) {
  404. _nonPanelPaymentFormProcess(
  405. std::make_shared<CreditsFormData>(data.data));
  406. close();
  407. }
  408. }, [&](const CreditsReceiptReady &data) {
  409. if (_nonPanelPaymentFormProcess) {
  410. _nonPanelPaymentFormProcess(
  411. std::make_shared<CreditsReceiptData>(data.data));
  412. close();
  413. }
  414. }, [&](const Error &error) {
  415. handleError(error);
  416. });
  417. }
  418. void CheckoutProcess::handleError(const Error &error) {
  419. const auto showToast = [&](TextWithEntities &&text) {
  420. _panel->requestActivate();
  421. _panel->showToast(std::move(text));
  422. };
  423. const auto &id = error.id;
  424. switch (error.type) {
  425. case Error::Type::Form:
  426. if (id == u"INVOICE_ALREADY_PAID"_q) {
  427. _panel->showCriticalError({
  428. tr::lng_payments_already_paid(tr::now)
  429. });
  430. } else if (true
  431. || id == u"PROVIDER_ACCOUNT_INVALID"_q
  432. || id == u"PROVIDER_ACCOUNT_TIMEOUT"_q) {
  433. _panel->showCriticalError({ "Error: " + id });
  434. }
  435. break;
  436. case Error::Type::Validate: {
  437. if (_submitState == SubmitState::Validating
  438. || _submitState == SubmitState::Validated) {
  439. _submitState = SubmitState::None;
  440. }
  441. if (_initialSilentValidation) {
  442. _initialSilentValidation = false;
  443. showForm();
  444. return;
  445. }
  446. using InfoField = Ui::InformationField;
  447. using CardField = Ui::CardField;
  448. if (id == u"REQ_INFO_NAME_INVALID"_q) {
  449. showInformationError(InfoField::Name);
  450. } else if (id == u"REQ_INFO_EMAIL_INVALID"_q) {
  451. showInformationError(InfoField::Email);
  452. } else if (id == u"REQ_INFO_PHONE_INVALID"_q) {
  453. showInformationError(InfoField::Phone);
  454. } else if (id == u"ADDRESS_STREET_LINE1_INVALID"_q) {
  455. showInformationError(InfoField::ShippingStreet);
  456. } else if (id == u"ADDRESS_CITY_INVALID"_q) {
  457. showInformationError(InfoField::ShippingCity);
  458. } else if (id == u"ADDRESS_STATE_INVALID"_q) {
  459. showInformationError(InfoField::ShippingState);
  460. } else if (id == u"ADDRESS_COUNTRY_INVALID"_q) {
  461. showInformationError(InfoField::ShippingCountry);
  462. } else if (id == u"ADDRESS_POSTCODE_INVALID"_q) {
  463. showInformationError(InfoField::ShippingPostcode);
  464. } else if (id == u"LOCAL_CARD_NUMBER_INVALID"_q) {
  465. showCardError(CardField::Number);
  466. } else if (id == u"LOCAL_CARD_EXPIRE_DATE_INVALID"_q) {
  467. showCardError(CardField::ExpireDate);
  468. } else if (id == u"LOCAL_CARD_CVC_INVALID"_q) {
  469. showCardError(CardField::Cvc);
  470. } else if (id == u"LOCAL_CARD_HOLDER_NAME_INVALID"_q) {
  471. showCardError(CardField::Name);
  472. } else if (id == u"LOCAL_CARD_BILLING_COUNTRY_INVALID"_q) {
  473. showCardError(CardField::AddressCountry);
  474. } else if (id == u"LOCAL_CARD_BILLING_ZIP_INVALID"_q) {
  475. showCardError(CardField::AddressZip);
  476. } else if (id == u"SHIPPING_BOT_TIMEOUT"_q) {
  477. showToast({ "Error: Bot Timeout!" });
  478. } else if (id == u"SHIPPING_NOT_AVAILABLE"_q) {
  479. showToast({ tr::lng_payments_shipping_not_available(tr::now) });
  480. } else {
  481. showToast({ "Error: " + id });
  482. }
  483. } break;
  484. case Error::Type::Stripe: {
  485. using Field = Ui::CardField;
  486. if (id == u"InvalidNumber"_q || id == u"IncorrectNumber"_q) {
  487. showCardError(Field::Number);
  488. } else if (id == u"InvalidCVC"_q || id == u"IncorrectCVC"_q) {
  489. showCardError(Field::Cvc);
  490. } else if (id == u"InvalidExpiryMonth"_q
  491. || id == u"InvalidExpiryYear"_q
  492. || id == u"ExpiredCard"_q) {
  493. showCardError(Field::ExpireDate);
  494. } else if (id == u"CardDeclined"_q) {
  495. showToast({ tr::lng_payments_card_declined(tr::now) });
  496. } else if (id == u"ProcessingError"_q) {
  497. showToast({ "Sorry, a processing error occurred." });
  498. } else {
  499. showToast({ "Stripe Error: " + id });
  500. }
  501. } break;
  502. case Error::Type::SmartGlocal: {
  503. showToast({ "SmartGlocal Error: " + id });
  504. } break;
  505. case Error::Type::TmpPassword:
  506. if (const auto box = _enterPasswordBox.data()) {
  507. if (!box->handleCustomCheckError(id)) {
  508. showToast({ "Error: Could not generate tmp password." });
  509. }
  510. }
  511. break;
  512. case Error::Type::Send:
  513. _sendFormFailed = true;
  514. if (const auto box = _enterPasswordBox.data()) {
  515. box->closeBox();
  516. }
  517. if (_submitState == SubmitState::Finishing) {
  518. UnregisterPaymentStart(this);
  519. _submitState = SubmitState::Validated;
  520. }
  521. if (id == u"INVOICE_ALREADY_PAID"_q) {
  522. showToast({ tr::lng_payments_already_paid(tr::now) });
  523. } else if (id == u"PAYMENT_FAILED"_q) {
  524. showToast({ tr::lng_payments_payment_failed(tr::now) });
  525. } else if (id == u"BOT_PRECHECKOUT_FAILED"_q) {
  526. showToast({ tr::lng_payments_precheckout_failed(tr::now) });
  527. } else if (id == u"BOT_PRECHECKOUT_TIMEOUT"_q) {
  528. showToast({ tr::lng_payments_precheckout_timeout(tr::now) });
  529. } else if (id == u"REQUESTED_INFO_INVALID"_q
  530. || id == u"SHIPPING_OPTION_INVALID"_q
  531. || id == u"PAYMENT_CREDENTIALS_INVALID"_q
  532. || id == u"PAYMENT_CREDENTIALS_ID_INVALID"_q) {
  533. showToast({ tr::lng_payments_payment_failed(tr::now) });
  534. showToast({ "Error: " + id + ". Your card has not been billed." });
  535. } else if (id == u"TMP_PASSWORD_INVALID"_q) {
  536. requestPassword();
  537. } else {
  538. showToast({ "Error: " + id });
  539. }
  540. break;
  541. default: Unexpected("Error type in CheckoutProcess::handleError.");
  542. }
  543. }
  544. void CheckoutProcess::panelRequestClose() {
  545. if (_form->hasChanges()) {
  546. _panel->showCloseConfirm();
  547. } else {
  548. panelCloseSure();
  549. }
  550. }
  551. void CheckoutProcess::panelCloseSure() {
  552. closeAndReactivate(_sendFormFailed
  553. ? CheckoutResult::Failed
  554. : _sendFormPending
  555. ? CheckoutResult::Pending
  556. : CheckoutResult::Cancelled);
  557. }
  558. void CheckoutProcess::closeAndReactivate(CheckoutResult result) {
  559. const auto reactivate = std::move(_reactivate);
  560. close();
  561. if (reactivate) {
  562. reactivate(result);
  563. }
  564. }
  565. void CheckoutProcess::close() {
  566. const auto i = Processes.find(_session);
  567. if (i == end(Processes)) {
  568. return;
  569. }
  570. auto &entry = i->second;
  571. const auto j = ranges::find(entry.byItem, this, [](const auto &pair) {
  572. return pair.second.get();
  573. });
  574. if (j != end(entry.byItem)) {
  575. entry.byItem.erase(j);
  576. }
  577. const auto k = ranges::find(entry.bySlug, this, [](const auto &pair) {
  578. return pair.second.get();
  579. });
  580. if (k != end(entry.bySlug)) {
  581. entry.bySlug.erase(k);
  582. }
  583. const auto l = ranges::find(
  584. entry.byRandomId,
  585. this,
  586. [](const auto &pair) { return pair.second.get(); });
  587. if (l != end(entry.byRandomId)) {
  588. entry.byRandomId.erase(l);
  589. }
  590. if (entry.byItem.empty()
  591. && entry.bySlug.empty()
  592. && i->second.byRandomId.empty()
  593. && entry.paymentStartedByItem.empty()
  594. && entry.paymentStartedBySlug.empty()) {
  595. Processes.erase(i);
  596. }
  597. }
  598. void CheckoutProcess::panelSubmit() {
  599. if (_form->invoice().receipt.paid) {
  600. closeAndReactivate(CheckoutResult::Paid);
  601. return;
  602. } else if (_submitState == SubmitState::Validating
  603. || _submitState == SubmitState::Finishing) {
  604. return;
  605. }
  606. const auto &method = _form->paymentMethod();
  607. const auto &invoice = _form->invoice();
  608. const auto &options = _form->shippingOptions();
  609. if (!options.list.empty() && options.selectedId.isEmpty()) {
  610. chooseShippingOption();
  611. } else if (_submitState != SubmitState::Validated
  612. && options.list.empty()
  613. && (invoice.isShippingAddressRequested
  614. || invoice.isNameRequested
  615. || invoice.isEmailRequested
  616. || invoice.isPhoneRequested)) {
  617. _submitState = SubmitState::Validating;
  618. _form->validateInformation(_form->information());
  619. } else if (!method.newCredentials
  620. && method.savedCredentialsIndex >= method.savedCredentials.size()) {
  621. editPaymentMethod();
  622. } else if (!invoice.termsUrl.isEmpty()
  623. && !_form->details().termsAccepted) {
  624. _panel->requestTermsAcceptance(
  625. _form->details().termsBotUsername,
  626. invoice.termsUrl,
  627. invoice.isRecurring);
  628. } else {
  629. RegisterPaymentStart(this, { _form->invoice().cover.title });
  630. _submitState = SubmitState::Finishing;
  631. _form->submit();
  632. }
  633. }
  634. void CheckoutProcess::panelTrustAndSubmit() {
  635. _form->trustBot();
  636. panelSubmit();
  637. }
  638. void CheckoutProcess::panelAcceptTermsAndSubmit() {
  639. _form->acceptTerms();
  640. panelSubmit();
  641. }
  642. void CheckoutProcess::panelWebviewMessage(
  643. const QJsonDocument &message,
  644. bool saveInformation) {
  645. if (!message.isArray()) {
  646. LOG(("Payments Error: "
  647. "Not an array received in buy_callback arguments."));
  648. return;
  649. }
  650. const auto list = message.array();
  651. if (list.at(0).toString() != "payment_form_submit") {
  652. return;
  653. } else if (!list.at(1).isString()) {
  654. LOG(("Payments Error: "
  655. "Not a string received in buy_callback result."));
  656. return;
  657. }
  658. auto error = QJsonParseError();
  659. const auto document = QJsonDocument::fromJson(
  660. list.at(1).toString().toUtf8(),
  661. &error);
  662. if (error.error != QJsonParseError::NoError) {
  663. LOG(("Payments Error: "
  664. "Failed to parse buy_callback arguments, error: %1."
  665. ).arg(error.errorString()));
  666. return;
  667. } else if (!document.isObject()) {
  668. LOG(("Payments Error: "
  669. "Not an object decoded in buy_callback result."));
  670. return;
  671. }
  672. const auto root = document.object();
  673. const auto title = root.value("title").toString();
  674. const auto credentials = root.value("credentials");
  675. if (!credentials.isObject()) {
  676. LOG(("Payments Error: "
  677. "Not an object received in payment credentials."));
  678. return;
  679. }
  680. crl::on_main(this, [=] {
  681. _form->setPaymentCredentials(NewCredentials{
  682. .title = title,
  683. .data = QJsonDocument(
  684. credentials.toObject()
  685. ).toJson(QJsonDocument::Compact),
  686. .saveOnServer = saveInformation,
  687. });
  688. });
  689. }
  690. std::optional<QDate> CheckoutProcess::panelOverrideExpireDateThreshold() {
  691. return _form->overrideExpireDateThreshold();
  692. }
  693. bool CheckoutProcess::panelWebviewNavigationAttempt(const QString &uri) {
  694. if (Core::TryConvertUrlToLocal(uri) == uri) {
  695. return true;
  696. }
  697. // #TODO payments
  698. crl::on_main(this, [=] { closeAndReactivate(CheckoutResult::Paid); });
  699. return false;
  700. }
  701. void CheckoutProcess::panelCancelEdit() {
  702. if (_submitState != SubmitState::None
  703. && _submitState != SubmitState::Validated) {
  704. return;
  705. }
  706. showForm();
  707. }
  708. void CheckoutProcess::panelEditPaymentMethod() {
  709. if (_submitState != SubmitState::None
  710. && _submitState != SubmitState::Validated) {
  711. return;
  712. }
  713. editPaymentMethod();
  714. }
  715. void CheckoutProcess::panelValidateCard(
  716. Ui::UncheckedCardDetails data,
  717. bool saveInformation) {
  718. _form->validateCard(data, saveInformation);
  719. }
  720. void CheckoutProcess::panelEditShippingInformation() {
  721. showEditInformation(Ui::InformationField::ShippingStreet);
  722. }
  723. void CheckoutProcess::panelEditName() {
  724. showEditInformation(Ui::InformationField::Name);
  725. }
  726. void CheckoutProcess::panelEditEmail() {
  727. showEditInformation(Ui::InformationField::Email);
  728. }
  729. void CheckoutProcess::panelEditPhone() {
  730. showEditInformation(Ui::InformationField::Phone);
  731. }
  732. void CheckoutProcess::showForm() {
  733. _panel->showForm(
  734. _form->invoice(),
  735. _form->information(),
  736. _form->paymentMethod().ui,
  737. _form->shippingOptions());
  738. if (_nonPanelPaymentFormProcess && !_realFormNotified) {
  739. _realFormNotified = true;
  740. const auto weak = base::make_weak(_panel.get());
  741. _nonPanelPaymentFormProcess(RealFormPresentedNotification());
  742. if (weak) {
  743. requestActivate();
  744. }
  745. }
  746. }
  747. void CheckoutProcess::showEditInformation(Ui::InformationField field) {
  748. if (_submitState != SubmitState::None
  749. && _submitState != SubmitState::Validated) {
  750. return;
  751. }
  752. _panel->showEditInformation(
  753. _form->invoice(),
  754. _form->information(),
  755. field);
  756. }
  757. void CheckoutProcess::showInformationError(Ui::InformationField field) {
  758. Expects(_submitState != SubmitState::Validated);
  759. if (_submitState != SubmitState::None) {
  760. return;
  761. }
  762. _panel->showInformationError(
  763. _form->invoice(),
  764. _form->information(),
  765. field);
  766. }
  767. void CheckoutProcess::showCardError(Ui::CardField field) {
  768. if (_submitState != SubmitState::None
  769. && _submitState != SubmitState::Validated) {
  770. return;
  771. }
  772. _panel->showCardError(_form->paymentMethod().ui.native, field);
  773. }
  774. void CheckoutProcess::chooseShippingOption() {
  775. _panel->chooseShippingOption(_form->shippingOptions());
  776. }
  777. void CheckoutProcess::chooseTips() {
  778. _panel->chooseTips(_form->invoice());
  779. }
  780. void CheckoutProcess::editPaymentMethod() {
  781. _panel->choosePaymentMethod(_form->paymentMethod().ui);
  782. }
  783. void CheckoutProcess::requestSetPassword() {
  784. _session->api().cloudPassword().reload();
  785. _panel->askSetPassword();
  786. }
  787. void CheckoutProcess::requestPassword() {
  788. getPasswordState([=](const Core::CloudPasswordState &state) {
  789. auto fields = PasscodeBox::CloudFields::From(state);
  790. fields.customTitle = tr::lng_payments_password_title();
  791. const auto &method = _form->paymentMethod();
  792. const auto &list = method.savedCredentials;
  793. const auto index = method.savedCredentialsIndex;
  794. fields.customDescription = tr::lng_payments_password_description(
  795. tr::now,
  796. lt_card,
  797. (index < list.size()) ? list[index].title : QString());
  798. fields.customSubmitButton = tr::lng_payments_password_submit();
  799. fields.customCheckCallback = [=](
  800. const Core::CloudPasswordResult &result,
  801. QPointer<PasscodeBox> box) {
  802. _enterPasswordBox = box;
  803. _form->submit(result);
  804. };
  805. _panel->showBox(Box<PasscodeBox>(_session, fields));
  806. });
  807. }
  808. void CheckoutProcess::panelSetPassword() {
  809. getPasswordState([=](const Core::CloudPasswordState &state) {
  810. if (state.hasPassword) {
  811. return;
  812. }
  813. auto owned = Box<PasscodeBox>(
  814. _session,
  815. PasscodeBox::CloudFields::From(state));
  816. const auto box = owned.data();
  817. rpl::merge(
  818. box->newPasswordSet() | rpl::to_empty,
  819. box->passwordReloadNeeded()
  820. ) | rpl::start_with_next([=] {
  821. _session->api().cloudPassword().reload();
  822. }, box->lifetime());
  823. box->clearUnconfirmedPassword(
  824. ) | rpl::start_with_next([=] {
  825. _session->api().cloudPassword().clearUnconfirmedPassword();
  826. }, box->lifetime());
  827. _panel->showBox(std::move(owned));
  828. });
  829. }
  830. void CheckoutProcess::panelOpenUrl(const QString &url) {
  831. File::OpenUrl(url);
  832. }
  833. void CheckoutProcess::getPasswordState(
  834. Fn<void(const Core::CloudPasswordState&)> callback) {
  835. Expects(callback != nullptr);
  836. if (_gettingPasswordState) {
  837. return;
  838. }
  839. _session->api().cloudPassword().state(
  840. ) | rpl::start_with_next([=](const Core::CloudPasswordState &state) {
  841. _gettingPasswordState.destroy();
  842. callback(state);
  843. }, _gettingPasswordState);
  844. }
  845. void CheckoutProcess::panelChooseShippingOption() {
  846. if (_submitState != SubmitState::None
  847. && _submitState != SubmitState::Validated) {
  848. return;
  849. }
  850. chooseShippingOption();
  851. }
  852. void CheckoutProcess::panelChangeShippingOption(const QString &id) {
  853. _form->setShippingOption(id);
  854. showForm();
  855. }
  856. void CheckoutProcess::panelChooseTips() {
  857. if (_submitState != SubmitState::None
  858. && _submitState != SubmitState::Validated) {
  859. return;
  860. }
  861. chooseTips();
  862. }
  863. void CheckoutProcess::panelChangeTips(int64 value) {
  864. _form->setTips(value);
  865. showForm();
  866. }
  867. void CheckoutProcess::panelValidateInformation(
  868. Ui::RequestedInformation data) {
  869. if (_submitState == SubmitState::Validated) {
  870. _submitState = SubmitState::None;
  871. }
  872. _form->validateInformation(data);
  873. }
  874. void CheckoutProcess::panelShowBox(object_ptr<Ui::BoxContent> box) {
  875. _panel->showBox(std::move(box));
  876. }
  877. QVariant CheckoutProcess::panelClickHandlerContext() {
  878. return QVariant::fromValue(ClickHandlerContext{
  879. .show = _panel->uiShow(),
  880. });
  881. }
  882. void CheckoutProcess::performInitialSilentValidation() {
  883. const auto &invoice = _form->invoice();
  884. const auto &saved = _form->information();
  885. if (invoice.receipt
  886. || (invoice.isNameRequested && saved.name.isEmpty())
  887. || (invoice.isEmailRequested && saved.email.isEmpty())
  888. || (invoice.isPhoneRequested && saved.phone.isEmpty())
  889. || (invoice.isShippingAddressRequested && !saved.shippingAddress)) {
  890. return;
  891. }
  892. _initialSilentValidation = true;
  893. _form->validateInformation(saved);
  894. }
  895. Webview::StorageId CheckoutProcess::panelWebviewStorageId() {
  896. return _session->local().resolveStorageIdOther();
  897. }
  898. Webview::ThemeParams CheckoutProcess::panelWebviewThemeParams() {
  899. return Window::Theme::WebViewParams();
  900. }
  901. } // namespace Payments