From 8cac76931e72bc61e97646828cf71de0be326130 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 30 Mar 2021 18:49:41 +0400 Subject: [PATCH] Support adding tips in payments. --- Telegram/Resources/langs/lang.strings | 3 + .../payments/payments_checkout_process.cpp | 19 ++ .../payments/payments_checkout_process.h | 3 + .../SourceFiles/payments/payments_form.cpp | 25 +- Telegram/SourceFiles/payments/payments_form.h | 1 + .../payments/ui/payments_field.cpp | 3 + .../SourceFiles/payments/ui/payments_field.h | 1 + .../payments/ui/payments_form_summary.cpp | 37 ++- .../payments/ui/payments_panel.cpp | 47 ++++ .../SourceFiles/payments/ui/payments_panel.h | 1 + .../payments/ui/payments_panel_data.h | 5 +- .../payments/ui/payments_panel_delegate.h | 2 + .../SourceFiles/ui/text/format_values.cpp | 241 ++++++++++++++++-- 13 files changed, 355 insertions(+), 33 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 2b6c73d04..97cab33a9 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1889,6 +1889,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_payments_billing_zip_code" = "Zip Code"; "lng_payments_save_payment_about" = "You can save your payment information for future use."; "lng_payments_save_information" = "Save Information"; +"lng_payments_tips_label" = "Tips"; +"lng_payments_tips_title" = "Tips"; +"lng_payments_tips_enter" = "Enter tips amount"; "lng_call_status_incoming" = "is calling you..."; "lng_call_status_connecting" = "connecting..."; diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.cpp b/Telegram/SourceFiles/payments/payments_checkout_process.cpp index aa4565a35..ac51ff9cf 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.cpp +++ b/Telegram/SourceFiles/payments/payments_checkout_process.cpp @@ -254,6 +254,8 @@ void CheckoutProcess::handleError(const Error &error) { || id == u"PAYMENT_CREDENTIALS_INVALID"_q || id == u"PAYMENT_CREDENTIALS_ID_INVALID"_q) { showToast({ "Error: " + id + ". Your card has not been billed." }); + } else { + showToast({ "Error: " + id }); } break; default: Unexpected("Error type in CheckoutProcess::handleError."); @@ -284,6 +286,7 @@ void CheckoutProcess::panelCloseSure() { void CheckoutProcess::panelSubmit() { if (_form->invoice().receipt.paid) { panelCloseSure(); + return; } else if (_submitState == SubmitState::Validation || _submitState == SubmitState::Finishing) { return; @@ -437,6 +440,10 @@ void CheckoutProcess::chooseShippingOption() { _panel->chooseShippingOption(_form->shippingOptions()); } +void CheckoutProcess::chooseTips() { + _panel->chooseTips(_form->invoice()); +} + void CheckoutProcess::editPaymentMethod() { _panel->choosePaymentMethod(_form->paymentMethod().ui); } @@ -453,6 +460,18 @@ void CheckoutProcess::panelChangeShippingOption(const QString &id) { showForm(); } +void CheckoutProcess::panelChooseTips() { + if (_submitState != SubmitState::None) { + return; + } + chooseTips(); +} + +void CheckoutProcess::panelChangeTips(int64 value) { + _form->setTips(value); + showForm(); +} + void CheckoutProcess::panelValidateInformation( Ui::RequestedInformation data) { _form->validateInformation(data); diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.h b/Telegram/SourceFiles/payments/payments_checkout_process.h index 794acdcec..53e85ed3c 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.h +++ b/Telegram/SourceFiles/payments/payments_checkout_process.h @@ -67,6 +67,7 @@ private: void showInformationError(Ui::InformationField field); void showCardError(Ui::CardField field); void chooseShippingOption(); + void chooseTips(); void editPaymentMethod(); void performInitialSilentValidation(); @@ -84,6 +85,8 @@ private: void panelEditPhone() override; void panelChooseShippingOption() override; void panelChangeShippingOption(const QString &id) override; + void panelChooseTips() override; + void panelChangeTips(int64 value) override; void panelValidateInformation(Ui::RequestedInformation data) override; void panelValidateCard(Ui::UncheckedCardDetails data) override; diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 7e183d625..ff9084857 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -45,6 +45,10 @@ namespace { }); } +[[nodiscard]] int64 ParsePriceAmount(uint64 value) { + return *reinterpret_cast(&value); +} + [[nodiscard]] std::vector ParsePrices( const MTPVector &data) { return ranges::views::all( @@ -53,7 +57,7 @@ namespace { return price.match([&](const MTPDlabeledPrice &data) { return Ui::LabeledPrice{ .label = qs(data.vlabel()), - .price = *reinterpret_cast(&data.vamount().v), + .price = ParsePriceAmount(data.vamount().v), }; }); }) | ranges::to_vector; @@ -290,6 +294,10 @@ void Form::processInvoice(const MTPDinvoice &data) { .cover = std::move(_invoice.cover), .prices = ParsePrices(data.vprices()), + .tipsMin = ParsePriceAmount(data.vmin_tip_amount().value_or_empty()), + .tipsMax = ParsePriceAmount(data.vmax_tip_amount().value_or_empty()), + .tipsSelected = ParsePriceAmount( + data.vdefault_tip_amount().value_or_empty()), .currency = qs(data.vcurrency()), .isNameRequested = data.is_name_requested(), @@ -330,8 +338,7 @@ void Form::processDetails(const MTPDpayments_paymentForm &data) { void Form::processDetails(const MTPDpayments_paymentReceipt &data) { _invoice.receipt = Ui::Receipt{ .date = data.vdate().v, - .totalAmount = *reinterpret_cast( - &data.vtotal_amount().v), + .totalAmount = ParsePriceAmount(data.vtotal_amount().v), .currency = qs(data.vcurrency()), .paid = true, }; @@ -440,7 +447,8 @@ void Form::submit() { : Flag::f_requested_info_id) | (_shippingOptions.selectedId.isEmpty() ? Flag(0) - : Flag::f_shipping_option_id)), + : Flag::f_shipping_option_id) + | (_invoice.tipsSelected ? Flag::f_tip_amount : Flag(0))), MTP_long(_details.formId), _peer->input, MTP_int(_msgId), @@ -449,7 +457,7 @@ void Form::submit() { MTP_inputPaymentCredentials( MTP_flags(0), MTP_dataJSON(MTP_bytes(_paymentMethod.newCredentials.data))), - MTP_long(0) // #TODO payments tip_amount + MTP_long(_invoice.tipsSelected) )).done([=](const MTPpayments_PaymentResult &result) { result.match([&](const MTPDpayments_paymentResult &data) { _updates.fire(PaymentFinished{ data.vupdates() }); @@ -668,6 +676,13 @@ void Form::setShippingOption(const QString &id) { _shippingOptions.selectedId = id; } +void Form::setTips(int64 value) { + _invoice.tipsSelected = std::clamp( + value, + _invoice.tipsMin, + _invoice.tipsMax); +} + void Form::processShippingOptions(const QVector &data) { _shippingOptions = Ui::ShippingOptions{ ranges::views::all( data diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index ca753eb31..ae37feece 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -173,6 +173,7 @@ public: void validateCard(const Ui::UncheckedCardDetails &details); void setPaymentCredentials(const NewCredentials &credentials); void setShippingOption(const QString &id); + void setTips(int64 value); void submit(); private: diff --git a/Telegram/SourceFiles/payments/ui/payments_field.cpp b/Telegram/SourceFiles/payments/ui/payments_field.cpp index fe958b207..20dc96160 100644 --- a/Telegram/SourceFiles/payments/ui/payments_field.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_field.cpp @@ -46,6 +46,7 @@ namespace { case FieldType::CardCVC: case FieldType::Country: case FieldType::Phone: + case FieldType::PriceAmount: return true; } Unexpected("FieldType in Payments::Ui::UseMaskedField."); @@ -67,6 +68,7 @@ namespace { case FieldType::CardCVC: case FieldType::Country: case FieldType::Phone: + case FieldType::PriceAmount: return base::make_unique_q(parent); } Unexpected("FieldType in Payments::Ui::CreateWrap."); @@ -94,6 +96,7 @@ namespace { case FieldType::CardExpireDate: case FieldType::CardCVC: case FieldType::Country: + case FieldType::PriceAmount: return CreateChild( wrap.get(), st::paymentsField, diff --git a/Telegram/SourceFiles/payments/ui/payments_field.h b/Telegram/SourceFiles/payments/ui/payments_field.h index 2061ff533..351b1a35e 100644 --- a/Telegram/SourceFiles/payments/ui/payments_field.h +++ b/Telegram/SourceFiles/payments/ui/payments_field.h @@ -29,6 +29,7 @@ enum class FieldType { Country, Phone, Email, + PriceAmount, }; struct FieldValidateRequest { diff --git a/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp b/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp index 32090c577..6ee83fd83 100644 --- a/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/vertical_layout.h" #include "ui/wrap/fade_wrap.h" #include "ui/text/format_values.h" +#include "ui/text/text_utilities.h" #include "data/data_countries.h" #include "lang/lang_keys.h" #include "base/unixtime.h" @@ -84,7 +85,7 @@ int64 FormSummary::computeTotalAmount() const { std::plus<>(), &LabeledPrice::price) : int64(0); - return total + shipping; + return total + shipping + _invoice.tipsSelected; } void FormSummary::setupControls() { @@ -193,12 +194,15 @@ void FormSummary::setupCover(not_null layout) { void FormSummary::setupPrices(not_null layout) { const auto addRow = [&]( const QString &label, - const QString &value, + const TextWithEntities &value, bool full = false) { const auto &st = full ? st::paymentsFullPriceAmount : st::paymentsPriceAmount; - const auto right = CreateChild(layout.get(), value, st); + const auto right = CreateChild( + layout.get(), + rpl::single(value), + st); const auto &padding = st::paymentsPricePadding; const auto left = layout->add( object_ptr( @@ -220,13 +224,14 @@ void FormSummary::setupPrices(not_null layout) { ) | rpl::start_with_next([=](int top, int width) { right->moveToRight(st::paymentsPricePadding.right(), top, width); }, right->lifetime()); + return right; }; Settings::AddSkip(layout, st::paymentsPricesTopSkip); if (_invoice.receipt) { addRow( tr::lng_payments_date_label(tr::now), - langDateTime(base::unixtime::parse(_invoice.receipt.date)), + { langDateTime(base::unixtime::parse(_invoice.receipt.date)) }, true); Settings::AddSkip(layout, st::paymentsPricesBottomSkip); Settings::AddDivider(layout); @@ -237,7 +242,7 @@ void FormSummary::setupPrices(not_null layout) { const QString &label, int64 amount, bool full = false) { - addRow(label, formatAmount(amount), full); + addRow(label, { formatAmount(amount) }, full); }; for (const auto &price : _invoice.prices) { add(price.label, price.price); @@ -251,7 +256,27 @@ void FormSummary::setupPrices(not_null layout) { add(price.label, price.price); } } - add(tr::lng_payments_total_label(tr::now), computeTotalAmount(), true); + + const auto computedTotal = computeTotalAmount(); + const auto total = _invoice.receipt.paid + ? _invoice.receipt.totalAmount + : computedTotal; + if (_invoice.receipt.paid) { + if (const auto tips = total - computedTotal) { + add(tr::lng_payments_tips_label(tr::now), tips); + } + } else if (_invoice.tipsMax > 0) { + const auto text = formatAmount(_invoice.tipsSelected); + const auto label = addRow( + tr::lng_payments_tips_label(tr::now), + Ui::Text::Link(text, "internal:edit_tips")); + label->setClickHandlerFilter([=](auto&&...) { + _delegate->panelChooseTips(); + return false; + }); + } + + add(tr::lng_payments_total_label(tr::now), total, true); Settings::AddSkip(layout, st::paymentsPricesBottomSkip); } diff --git a/Telegram/SourceFiles/payments/ui/payments_panel.cpp b/Telegram/SourceFiles/payments/ui/payments_panel.cpp index f5c7015cc..35ab4df37 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_panel.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "payments/ui/payments_edit_information.h" #include "payments/ui/payments_edit_card.h" #include "payments/ui/payments_panel_delegate.h" +#include "payments/ui/payments_field.h" #include "ui/widgets/separate_panel.h" #include "ui/boxes/single_choice_box.h" #include "lang/lang_keys.h" @@ -19,6 +20,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_passport.h" namespace Payments::Ui { +namespace { + +[[nodiscard]] auto PriceAmountValidator(int64 min, int64 max) { + return [=](FieldValidateRequest request) { + return FieldValidateResult{ + .value = request.nowValue, + .position = request.nowPosition, + }; + }; +} + +} // namespace Panel::Panel(not_null delegate) : _delegate(delegate) @@ -127,6 +140,40 @@ void Panel::chooseShippingOption(const ShippingOptions &options) { })); } +void Panel::chooseTips(const Invoice &invoice) { + const auto min = invoice.tipsMin; + const auto max = invoice.tipsMax; + const auto now = invoice.tipsSelected; + showBox(Box([=](not_null box) { + box->setTitle(tr::lng_payments_tips_title()); + + const auto row = box->lifetime().make_state( + box, + FieldConfig{ + .type = FieldType::PriceAmount, + .placeholder = tr::lng_payments_tips_enter(), + .value = QString::number(now), + .validator = PriceAmountValidator(min, max), + }); + box->setFocusCallback([=] { + row->setFocusFast(); + }); + box->addRow(row->ownedWidget()); + box->addRow(object_ptr(box, "Min: " + QString::number(min), st::defaultFlatLabel)); + box->addRow(object_ptr(box, "Max: " + QString::number(max), st::defaultFlatLabel)); + box->addButton(tr::lng_settings_save(), [=] { + const auto value = row->value().toLongLong(); + if (value < min || value > max) { + row->showError(); + } else { + _delegate->panelChangeTips(value); + box->closeBox(); + } + }); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + })); +} + void Panel::showEditPaymentMethod(const PaymentMethodDetails &method) { _widget->setTitle(tr::lng_payments_card_title()); if (method.native.supported) { diff --git a/Telegram/SourceFiles/payments/ui/payments_panel.h b/Telegram/SourceFiles/payments/ui/payments_panel.h index e07703ba9..6d39159d3 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel.h +++ b/Telegram/SourceFiles/payments/ui/payments_panel.h @@ -63,6 +63,7 @@ public: const NativeMethodDetails &native, CardField field); void chooseShippingOption(const ShippingOptions &options); + void chooseTips(const Invoice &invoice); void choosePaymentMethod(const PaymentMethodDetails &method); bool showWebview(const QString &url, bool allowBack); diff --git a/Telegram/SourceFiles/payments/ui/payments_panel_data.h b/Telegram/SourceFiles/payments/ui/payments_panel_data.h index e530bf6b7..13efeeff6 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel_data.h +++ b/Telegram/SourceFiles/payments/ui/payments_panel_data.h @@ -39,6 +39,9 @@ struct Invoice { Cover cover; std::vector prices; + int64 tipsMin = 0; + int64 tipsMax = 0; + int64 tipsSelected = 0; QString currency; Receipt receipt; @@ -53,7 +56,7 @@ struct Invoice { bool emailSentToProvider = false; [[nodiscard]] bool valid() const { - return !currency.isEmpty() && !prices.empty(); + return !currency.isEmpty() && (!prices.empty() || tipsMax); } [[nodiscard]] explicit operator bool() const { return valid(); diff --git a/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h b/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h index 8b6124604..e3bf155e7 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h +++ b/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h @@ -38,6 +38,8 @@ public: virtual void panelEditPhone() = 0; virtual void panelChooseShippingOption() = 0; virtual void panelChangeShippingOption(const QString &id) = 0; + virtual void panelChooseTips() = 0; + virtual void panelChangeTips(int64 value) = 0; virtual void panelValidateInformation(RequestedInformation data) = 0; virtual void panelValidateCard(Ui::UncheckedCardDetails data) = 0; diff --git a/Telegram/SourceFiles/ui/text/format_values.cpp b/Telegram/SourceFiles/ui/text/format_values.cpp index b614fe14c..4b41f6d84 100644 --- a/Telegram/SourceFiles/ui/text/format_values.cpp +++ b/Telegram/SourceFiles/ui/text/format_values.cpp @@ -10,6 +10,40 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include +#include +#include +#include + +[[nodiscard]] QString FormatWithSeparators( + double amount, + char decimal, + char thousands) { + Expects(decimal != 0); + + // Thanks https://stackoverflow.com/a/5058949 + struct FormattingHelper : std::numpunct { + FormattingHelper(char decimal, char thousands) + : decimal(decimal) + , thousands(thousands) { + } + + char do_decimal_point() const override { return decimal; } + char do_thousands_sep() const override { return thousands; } + + char decimal = '.'; + char thousands = ','; + }; + + auto stream = std::ostringstream(); + auto helper = FormattingHelper(decimal, thousands ? thousands : '?'); + stream.imbue(std::locale(stream.getloc(), &helper)); + stream << std::fixed << amount; + auto result = QString::fromStdString(stream.str()); + if (!thousands) { + result.replace('?', QString()); + } + return result; +} namespace Ui { @@ -126,13 +160,168 @@ QString FormatPlayedText(qint64 played, qint64 duration) { } QString FillAmountAndCurrency(int64 amount, const QString ¤cy) { - static const auto ShortCurrencyNames = QMap{ - { u"USD"_q, QString::fromUtf8("\x24") }, - { u"GBP"_q, QString::fromUtf8("\xC2\xA3") }, - { u"EUR"_q, QString::fromUtf8("\xE2\x82\xAC") }, - { u"JPY"_q, QString::fromUtf8("\xC2\xA5") }, + struct Rule { + //const char *name = ""; + //const char *native = ""; + const char *international = ""; + char thousands = ','; + char decimal = '.'; + bool left = true; + bool space = false; }; - static const auto Denominators = QMap{ + static const auto kRules = std::vector>{ + { u"AED"_q, { "", ',', '.', true, true } }, + { u"AFN"_q, {} }, + { u"ALL"_q, { "", '.', ',', false } }, + { u"AMD"_q, { "", ',', '.', false, true } }, + { u"ARS"_q, { "", '.', ',', true, true } }, + { u"AUD"_q, { "AU$" } }, + { u"AZN"_q, { "", ' ', ',', false, true } }, + { u"BAM"_q, { "", '.', ',', false, true } }, + { u"BDT"_q, { "", ',', '.', true, true } }, + { u"BGN"_q, { "", ' ', ',', false, true } }, + { u"BND"_q, { "", '.', ',', } }, + { u"BOB"_q, { "", '.', ',', true, true } }, + { u"BRL"_q, { "R$", '.', ',', true, true } }, + { u"BHD"_q, { "", ',', '.', true, true } }, + { u"BYR"_q, { "", ' ', ',', false, true } }, + { u"CAD"_q, { "CA$" } }, + { u"CHF"_q, { "", '\'', '.', false, true } }, + { u"CLP"_q, { "", '.', ',', true, true } }, + { u"CNY"_q, { "\x43\x4E\xC2\xA5" } }, + { u"COP"_q, { "", '.', ',', true, true } }, + { u"CRC"_q, { "", '.', ',', } }, + { u"CZK"_q, { "", ' ', ',', false, true } }, + { u"DKK"_q, { "", '\0', ',', false, true } }, + { u"DOP"_q, {} }, + { u"DZD"_q, { "", ',', '.', true, true } }, + { u"EGP"_q, { "", ',', '.', true, true } }, + { u"EUR"_q, { "\xE2\x82\xAC", ' ', ',', false, true } }, + { u"GBP"_q, { "\xC2\xA3" } }, + { u"GEL"_q, { "", ' ', ',', false, true } }, + { u"GTQ"_q, {} }, + { u"HKD"_q, { "HK$" } }, + { u"HNL"_q, { "", ',', '.', true, true } }, + { u"HRK"_q, { "", '.', ',', false, true } }, + { u"HUF"_q, { "", ' ', ',', false, true } }, + { u"IDR"_q, { "", '.', ',', } }, + { u"ILS"_q, { "\xE2\x82\xAA", ',', '.', true, true } }, + { u"INR"_q, { "\xE2\x82\xB9" } }, + { u"ISK"_q, { "", '.', ',', false, true } }, + { u"JMD"_q, {} }, + { u"JPY"_q, { "\xC2\xA5" } }, + { u"KES"_q, {} }, + { u"KGS"_q, { "", ' ', '-', false, true } }, + { u"KRW"_q, { "\xE2\x82\xA9" } }, + { u"KZT"_q, { "", ' ', '-', } }, + { u"LBP"_q, { "", ',', '.', true, true } }, + { u"LKR"_q, { "", ',', '.', true, true } }, + { u"MAD"_q, { "", ',', '.', true, true } }, + { u"MDL"_q, { "", ',', '.', false, true } }, + { u"MNT"_q, { "", ' ', ',', } }, + { u"MUR"_q, {} }, + { u"MVR"_q, { "", ',', '.', false, true } }, + { u"MXN"_q, { "MX$" } }, + { u"MYR"_q, {} }, + { u"MZN"_q, {} }, + { u"NGN"_q, {} }, + { u"NIO"_q, { "", ',', '.', true, true } }, + { u"NOK"_q, { "", ' ', ',', true, true } }, + { u"NPR"_q, {} }, + { u"NZD"_q, { "NZ$" } }, + { u"PAB"_q, { "", ',', '.', true, true } }, + { u"PEN"_q, { "", ',', '.', true, true } }, + { u"PHP"_q, {} }, + { u"PKR"_q, {} }, + { u"PLN"_q, { "", ' ', ',', false, true } }, + { u"PYG"_q, { "", '.', ',', true, true } }, + { u"QAR"_q, { "", ',', '.', true, true } }, + { u"RON"_q, { "", '.', ',', false, true } }, + { u"RSD"_q, { "", '.', ',', false, true } }, + { u"RUB"_q, { "", ' ', ',', false, true } }, + { u"SAR"_q, { "", ',', '.', true, true } }, + { u"SEK"_q, { "", '.', ',', false, true } }, + { u"SGD"_q, {} }, + { u"THB"_q, { "\xE0\xB8\xBF" } }, + { u"TJS"_q, { "", ' ', ';', false, true } }, + { u"TRY"_q, { "", '.', ',', false, true } }, + { u"TTD"_q, {} }, + { u"TWD"_q, { "NT$" } }, + { u"TZS"_q, {} }, + { u"UAH"_q, { "", ' ', ',', false } }, + { u"UGX"_q, {} }, + { u"USD"_q, { "$" } }, + { u"UYU"_q, { "", '.', ',', true, true } }, + { u"UZS"_q, { "", ' ', ',', false, true } }, + { u"VND"_q, { "\xE2\x82\xAB", '.', ',', false, true } }, + { u"YER"_q, { "", ',', '.', true, true } }, + { u"ZAR"_q, { "", ',', '.', true, true } }, + { u"IRR"_q, { "", ',', '/', false, true } }, + { u"IQD"_q, { "", ',', '.', true, true } }, + { u"VEF"_q, { "", '.', ',', true, true } }, + { u"SYP"_q, { "", ',', '.', true, true } }, + + //{ u"VUV"_q, { "", ',', '.', false } }, + //{ u"WST"_q, {} }, + //{ u"XAF"_q, { "FCFA", ',', '.', false } }, + //{ u"XCD"_q, {} }, + //{ u"XOF"_q, { "CFA", ' ', ',', false } }, + //{ u"XPF"_q, { "", ',', '.', false } }, + //{ u"ZMW"_q, {} }, + //{ u"ANG"_q, {} }, + //{ u"RWF"_q, { "", ' ', ',', true, true } }, + //{ u"PGK"_q, {} }, + //{ u"TOP"_q, {} }, + //{ u"SBD"_q, {} }, + //{ u"SCR"_q, {} }, + //{ u"SHP"_q, {} }, + //{ u"SLL"_q, {} }, + //{ u"SOS"_q, {} }, + //{ u"SRD"_q, {} }, + //{ u"STD"_q, {} }, + //{ u"SVC"_q, {} }, + //{ u"SZL"_q, {} }, + //{ u"AOA"_q, {} }, + //{ u"AWG"_q, {} }, + //{ u"BBD"_q, {} }, + //{ u"BIF"_q, { "", ',', '.', false } }, + //{ u"BMD"_q, {} }, + //{ u"BSD"_q, {} }, + //{ u"BWP"_q, {} }, + //{ u"BZD"_q, {} }, + //{ u"CDF"_q, { "", ',', '.', false } }, + //{ u"CVE"_q, {} }, + //{ u"DJF"_q, { "", ',', '.', false } }, + //{ u"ETB"_q, {} }, + //{ u"FJD"_q, {} }, + //{ u"FKP"_q, {} }, + //{ u"GIP"_q, {} }, + //{ u"GMD"_q, { "", ',', '.', false } }, + //{ u"GNF"_q, { "", ',', '.', false } }, + //{ u"GYD"_q, {} }, + //{ u"HTG"_q, {} }, + //{ u"KHR"_q, { "", ',', '.', false } }, + //{ u"KMF"_q, { "", ',', '.', false } }, + //{ u"KYD"_q, {} }, + //{ u"LAK"_q, { "", ',', '.', false } }, + //{ u"LRD"_q, {} }, + //{ u"LSL"_q, { "", ',', '.', false } }, + //{ u"MGA"_q, {} }, + //{ u"MKD"_q, { "", '.', ',', false, true } }, + //{ u"MOP"_q, {} }, + //{ u"MWK"_q, {} }, + //{ u"NAD"_q, {} }, + }; + static const auto kRulesMap = [] { + // flat_multi_map_pair_type lacks some required constructors :( + auto &&pairs = kRules | ranges::views::transform([](auto &&pair) { + return base::flat_multi_map_pair_type( + pair.first, + pair.second); + }); + return base::flat_map(begin(pairs), end(pairs)); + }(); + static const auto kDenominators = base::flat_map{ { u"CLF"_q, 10000 }, { u"BHD"_q, 1000 }, { u"IQD"_q, 1000 }, @@ -163,21 +352,31 @@ QString FillAmountAndCurrency(int64 amount, const QString ¤cy) { { u"XPF"_q, 1 }, { u"MRO"_q, 10 }, }; - const auto currencyText = ShortCurrencyNames.value(currency, currency); - const auto denominator = Denominators.value(currency, 100); - const auto currencyValue = amount / float64(denominator); - const auto digits = [&] { - auto result = 0; - for (auto test = 1; test < denominator; test *= 10) { - ++result; - } - return result; - }(); - return QLocale::system().toCurrencyString(currencyValue, currencyText); - //auto amountBucks = amount / 100; - //auto amountCents = amount % 100; - //auto amountText = u"%1,%2").arg(amountBucks).arg(amountCents, 2, 10, QChar('0')); - //return currencyText + amountText; + const auto denominatorIt = kDenominators.find(currency); + const auto denominator = (denominatorIt != end(kDenominators)) + ? denominatorIt->second + : 100; + const auto value = amount / float64(denominator); + const auto ruleIt = kRulesMap.find(currency); + if (ruleIt == end(kRulesMap)) { + return QLocale::system().toCurrencyString(value, currency); + } + const auto &rule = ruleIt->second; + const auto name = (*rule.international) + ? QString::fromUtf8(rule.international) + : currency; + auto result = QString(); + if (rule.left) { + result.append(name); + if (rule.space) result.append(' '); + } + result.append( + FormatWithSeparators(value, rule.decimal, rule.thousands)); + if (!rule.left) { + if (rule.space) result.append(' '); + result.append(name); + } + return result; } QString ComposeNameString(