admin panel have more control over payment now
This commit is contained in:
@@ -368,6 +368,7 @@ country-pl = Poland
|
|||||||
country-hu = Hungary
|
country-hu = Hungary
|
||||||
checkout-note = Order note
|
checkout-note = Order note
|
||||||
checkout-save-profile = Save residence address to my profile
|
checkout-save-profile = Save residence address to my profile
|
||||||
|
payment-none = No payment method is currently available.
|
||||||
account-type = Account type
|
account-type = Account type
|
||||||
account-personal = Individual
|
account-personal = Individual
|
||||||
account-company = Company
|
account-company = Company
|
||||||
@@ -483,6 +484,11 @@ bank-variable-symbol = Variable symbol
|
|||||||
bank-amount = Amount
|
bank-amount = Amount
|
||||||
admin-shipping = Shipping
|
admin-shipping = Shipping
|
||||||
admin-shipping-desc = set the price and availability of each delivery option.
|
admin-shipping-desc = set the price and availability of each delivery option.
|
||||||
|
admin-payments = Payments
|
||||||
|
admin-payments-desc = enable or disable payment methods and edit bank-transfer details.
|
||||||
|
payment-methods = Payment methods
|
||||||
|
payment-enabled = Active
|
||||||
|
payment-bank-settings = Bank transfer details
|
||||||
shipping-enabled = Active
|
shipping-enabled = Active
|
||||||
admin-currency = Exchange rate
|
admin-currency = Exchange rate
|
||||||
admin-currency-desc = set the exchange rate for the currencies customers can switch between. You always enter prices in EUR.
|
admin-currency-desc = set the exchange rate for the currencies customers can switch between. You always enter prices in EUR.
|
||||||
|
|||||||
@@ -368,6 +368,7 @@ country-pl = Poľsko
|
|||||||
country-hu = Maďarsko
|
country-hu = Maďarsko
|
||||||
checkout-note = Poznámka k objednávke
|
checkout-note = Poznámka k objednávke
|
||||||
checkout-save-profile = Uložiť adresu bydliska do môjho profilu
|
checkout-save-profile = Uložiť adresu bydliska do môjho profilu
|
||||||
|
payment-none = Momentálne nie je dostupný žiadny spôsob platby.
|
||||||
account-type = Typ účtu
|
account-type = Typ účtu
|
||||||
account-personal = Súkromná osoba
|
account-personal = Súkromná osoba
|
||||||
account-company = Firma
|
account-company = Firma
|
||||||
@@ -483,6 +484,11 @@ bank-variable-symbol = Variabilný symbol
|
|||||||
bank-amount = Suma
|
bank-amount = Suma
|
||||||
admin-shipping = Doprava
|
admin-shipping = Doprava
|
||||||
admin-shipping-desc = nastaviť cenu a dostupnosť jednotlivých možností dopravy.
|
admin-shipping-desc = nastaviť cenu a dostupnosť jednotlivých možností dopravy.
|
||||||
|
admin-payments = Platby
|
||||||
|
admin-payments-desc = zapnite alebo vypnite spôsoby platby a upravte údaje pre prevod na účet.
|
||||||
|
payment-methods = Spôsoby platby
|
||||||
|
payment-enabled = Aktívne
|
||||||
|
payment-bank-settings = Údaje pre prevod na účet
|
||||||
shipping-enabled = Aktívne
|
shipping-enabled = Aktívne
|
||||||
admin-currency = Kurz
|
admin-currency = Kurz
|
||||||
admin-currency-desc = nastaviť výmenný kurz pre meny, medzi ktorými môžu zákazníci prepínať. Ceny zadávate vždy v EUR.
|
admin-currency-desc = nastaviť výmenný kurz pre meny, medzi ktorými môžu zákazníci prepínať. Ceny zadávate vždy v EUR.
|
||||||
|
|||||||
@@ -105,6 +105,10 @@
|
|||||||
class="flex items-center gap-2 rounded-radius px-2 py-1.5 text-sm font-medium text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-on-surface-strong focus:outline-hidden focus-visible:underline aria-[current=page]:bg-primary/10 aria-[current=page]:text-on-surface-strong dark:text-on-surface-dark dark:hover:bg-primary-dark/5 dark:hover:text-on-surface-dark-strong dark:aria-[current=page]:bg-primary-dark/10 dark:aria-[current=page]:text-on-surface-dark-strong">
|
class="flex items-center gap-2 rounded-radius px-2 py-1.5 text-sm font-medium text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-on-surface-strong focus:outline-hidden focus-visible:underline aria-[current=page]:bg-primary/10 aria-[current=page]:text-on-surface-strong dark:text-on-surface-dark dark:hover:bg-primary-dark/5 dark:hover:text-on-surface-dark-strong dark:aria-[current=page]:bg-primary-dark/10 dark:aria-[current=page]:text-on-surface-dark-strong">
|
||||||
{{ t(key="admin-shipping", lang=lang | default(value='sk')) }}
|
{{ t(key="admin-shipping", lang=lang | default(value='sk')) }}
|
||||||
</a>
|
</a>
|
||||||
|
<a href="/admin/payments" data-nav="/admin/payments"
|
||||||
|
class="flex items-center gap-2 rounded-radius px-2 py-1.5 text-sm font-medium text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-on-surface-strong focus:outline-hidden focus-visible:underline aria-[current=page]:bg-primary/10 aria-[current=page]:text-on-surface-strong dark:text-on-surface-dark dark:hover:bg-primary-dark/5 dark:hover:text-on-surface-dark-strong dark:aria-[current=page]:bg-primary-dark/10 dark:aria-[current=page]:text-on-surface-dark-strong">
|
||||||
|
{{ t(key="admin-payments", lang=lang | default(value='sk')) }}
|
||||||
|
</a>
|
||||||
<a href="/admin/currencies" data-nav="/admin/currencies"
|
<a href="/admin/currencies" data-nav="/admin/currencies"
|
||||||
class="flex items-center gap-2 rounded-radius px-2 py-1.5 text-sm font-medium text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-on-surface-strong focus:outline-hidden focus-visible:underline aria-[current=page]:bg-primary/10 aria-[current=page]:text-on-surface-strong dark:text-on-surface-dark dark:hover:bg-primary-dark/5 dark:hover:text-on-surface-dark-strong dark:aria-[current=page]:bg-primary-dark/10 dark:aria-[current=page]:text-on-surface-dark-strong">
|
class="flex items-center gap-2 rounded-radius px-2 py-1.5 text-sm font-medium text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-on-surface-strong focus:outline-hidden focus-visible:underline aria-[current=page]:bg-primary/10 aria-[current=page]:text-on-surface-strong dark:text-on-surface-dark dark:hover:bg-primary-dark/5 dark:hover:text-on-surface-dark-strong dark:aria-[current=page]:bg-primary-dark/10 dark:aria-[current=page]:text-on-surface-dark-strong">
|
||||||
{{ t(key="admin-currency", lang=lang | default(value='sk')) }}
|
{{ t(key="admin-currency", lang=lang | default(value='sk')) }}
|
||||||
|
|||||||
47
assets/views/admin/payments/index.html
Normal file
47
assets/views/admin/payments/index.html
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{% extends "admin/base.html" %}
|
||||||
|
{% import "macros/ui.html" as ui %}
|
||||||
|
|
||||||
|
{% block title %}{{ t(key="admin-payments", lang=lang | default(value='sk')) }}{% endblock title %}
|
||||||
|
{% block crumb %}{{ t(key="admin-payments", lang=lang | default(value='sk')) }}{% endblock crumb %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<header class="space-y-1">
|
||||||
|
<h1 class="text-2xl font-bold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="admin-payments", lang=lang | default(value='sk')) }}</h1>
|
||||||
|
<p class="text-sm text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="admin-payments-desc", lang=lang | default(value='sk')) }}</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="mt-6 space-y-4">
|
||||||
|
<h2 class="text-lg font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="payment-methods", lang=lang | default(value='sk')) }}</h2>
|
||||||
|
{% for method in methods %}
|
||||||
|
<form method="post" action="/admin/payments/methods/{{ method.id }}"
|
||||||
|
class="flex flex-wrap items-center gap-4 rounded-radius border border-outline bg-surface p-5 dark:border-outline-dark dark:bg-surface-dark-alt">
|
||||||
|
{{ ui::csrf_field() }}
|
||||||
|
<div class="min-w-40">
|
||||||
|
<p class="font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key=method.label_key, lang=lang | default(value='sk')) }}</p>
|
||||||
|
<p class="text-xs text-on-surface/60 dark:text-on-surface-dark/60">{{ method.code }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="pb-1">{{ ui::checkbox(name="enabled", label=t(key="payment-enabled", lang=lang | default(value='sk')), checked=method.enabled) }}</div>
|
||||||
|
{{ ui::button(label=t(key="save", lang=lang | default(value='sk')), type="submit", extra="ml-auto") }}
|
||||||
|
</form>
|
||||||
|
{% endfor %}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="mt-8 space-y-4">
|
||||||
|
<h2 class="text-lg font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="payment-bank-settings", lang=lang | default(value='sk')) }}</h2>
|
||||||
|
<form method="post" action="/admin/payments/bank"
|
||||||
|
class="space-y-4 rounded-radius border border-outline bg-surface p-5 dark:border-outline-dark dark:bg-surface-dark-alt">
|
||||||
|
{{ ui::csrf_field() }}
|
||||||
|
<div class="space-y-1.5">
|
||||||
|
<label for="bank_account_name" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="bank-account-name", lang=lang | default(value='sk')) }}</label>
|
||||||
|
{{ ui::input(name="bank_account_name", id="bank_account_name", value=bank_account_name) }}
|
||||||
|
</div>
|
||||||
|
<div class="space-y-1.5">
|
||||||
|
<label for="bank_iban" class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">IBAN</label>
|
||||||
|
{{ ui::input(name="bank_iban", id="bank_iban", value=bank_iban) }}
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
{{ ui::button(label=t(key="save", lang=lang | default(value='sk')), type="submit") }}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
{% endblock content %}
|
||||||
@@ -266,14 +266,16 @@
|
|||||||
<!-- payment -->
|
<!-- payment -->
|
||||||
<fieldset class="space-y-3 rounded-radius border border-outline bg-surface p-6 dark:border-outline-dark dark:bg-surface-dark-alt">
|
<fieldset class="space-y-3 rounded-radius border border-outline bg-surface p-6 dark:border-outline-dark dark:bg-surface-dark-alt">
|
||||||
<legend class="px-1 text-sm font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-payment", lang=lang | default(value='sk')) }}{{ ui::req() }}</legend>
|
<legend class="px-1 text-sm font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="checkout-payment", lang=lang | default(value='sk')) }}{{ ui::req() }}</legend>
|
||||||
|
{% if payment_methods | length > 0 %}
|
||||||
|
{% for method in payment_methods %}
|
||||||
<label class="flex cursor-pointer items-center gap-3 rounded-radius border border-outline px-4 py-3 transition has-[:checked]:border-primary dark:border-outline-dark dark:has-[:checked]:border-primary-dark">
|
<label class="flex cursor-pointer items-center gap-3 rounded-radius border border-outline px-4 py-3 transition has-[:checked]:border-primary dark:border-outline-dark dark:has-[:checked]:border-primary-dark">
|
||||||
{{ ui::radio(name="payment_method", value="cod", attrs='required x-model="paymentMethod"') }}
|
{{ ui::radio(name="payment_method", value=method.code, attrs='required x-model="paymentMethod"') }}
|
||||||
<span class="font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="payment-cod", lang=lang | default(value='sk')) }}</span>
|
<span class="font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key=method.label_key, lang=lang | default(value='sk')) }}</span>
|
||||||
</label>
|
|
||||||
<label class="flex cursor-pointer items-center gap-3 rounded-radius border border-outline px-4 py-3 transition has-[:checked]:border-primary dark:border-outline-dark dark:has-[:checked]:border-primary-dark">
|
|
||||||
{{ ui::radio(name="payment_method", value="bank_transfer", attrs='required x-model="paymentMethod"') }}
|
|
||||||
<span class="font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="payment-bank", lang=lang | default(value='sk')) }}</span>
|
|
||||||
</label>
|
</label>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<p class="text-sm text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="payment-none", lang=lang | default(value='sk')) }}</p>
|
||||||
|
{% endif %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<div class="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ mod m20260623_000003_drop_currency;
|
|||||||
mod m20260623_000004_currencies;
|
mod m20260623_000004_currencies;
|
||||||
mod m20260625_000001_add_avatar_to_users;
|
mod m20260625_000001_add_avatar_to_users;
|
||||||
mod m20260627_000001_order_residence_address;
|
mod m20260627_000001_order_residence_address;
|
||||||
|
mod m20260627_000002_payment_settings;
|
||||||
pub struct Migrator;
|
pub struct Migrator;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
@@ -106,6 +107,7 @@ impl MigratorTrait for Migrator {
|
|||||||
Box::new(m20260623_000004_currencies::Migration),
|
Box::new(m20260623_000004_currencies::Migration),
|
||||||
Box::new(m20260625_000001_add_avatar_to_users::Migration),
|
Box::new(m20260625_000001_add_avatar_to_users::Migration),
|
||||||
Box::new(m20260627_000001_order_residence_address::Migration),
|
Box::new(m20260627_000001_order_residence_address::Migration),
|
||||||
|
Box::new(m20260627_000002_payment_settings::Migration),
|
||||||
// inject-above (do not remove this comment)
|
// inject-above (do not remove this comment)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
41
migration/src/m20260627_000002_payment_settings.rs
Normal file
41
migration/src/m20260627_000002_payment_settings.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
use loco_rs::schema::*;
|
||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
#[derive(DeriveMigrationName)]
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
async fn up(&self, m: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
create_table(
|
||||||
|
m,
|
||||||
|
"payment_methods",
|
||||||
|
&[
|
||||||
|
("id", ColType::PkAuto),
|
||||||
|
("code", ColType::StringUniq),
|
||||||
|
("name", ColType::String),
|
||||||
|
("enabled", ColType::BooleanWithDefault(true)),
|
||||||
|
("position", ColType::IntegerWithDefault(0)),
|
||||||
|
],
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
create_table(
|
||||||
|
m,
|
||||||
|
"shop_settings",
|
||||||
|
&[
|
||||||
|
("id", ColType::PkAuto),
|
||||||
|
("key", ColType::StringUniq),
|
||||||
|
("value", ColType::TextNull),
|
||||||
|
],
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
drop_table(m, "shop_settings").await?;
|
||||||
|
drop_table(m, "payment_methods").await
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ use std::{path::Path, sync::Arc};
|
|||||||
use crate::{
|
use crate::{
|
||||||
controllers::{
|
controllers::{
|
||||||
account, admin_categories, admin_currencies, admin_customers, admin_dashboard,
|
account, admin_categories, admin_currencies, admin_customers, admin_dashboard,
|
||||||
admin_discount_profiles, admin_form, admin_orders, admin_products, admin_shipping,
|
admin_discount_profiles, admin_form, admin_orders, admin_payments, admin_products, admin_shipping,
|
||||||
auth, auth_pages, cart, checkout, currency, home, i18n, media, oauth2,
|
auth, auth_pages, cart, checkout, currency, home, i18n, media, oauth2,
|
||||||
pages, shop,
|
pages, shop,
|
||||||
},
|
},
|
||||||
@@ -83,6 +83,7 @@ impl Hooks for App {
|
|||||||
Box::new(initializers::view_engine::ViewEngineInitializer),
|
Box::new(initializers::view_engine::ViewEngineInitializer),
|
||||||
Box::new(initializers::admin_seeder::AdminSeeder),
|
Box::new(initializers::admin_seeder::AdminSeeder),
|
||||||
Box::new(initializers::shipping_seeder::ShippingSeeder),
|
Box::new(initializers::shipping_seeder::ShippingSeeder),
|
||||||
|
Box::new(initializers::payment_seeder::PaymentSeeder),
|
||||||
Box::new(initializers::currency_seeder::CurrencySeeder),
|
Box::new(initializers::currency_seeder::CurrencySeeder),
|
||||||
Box::new(initializers::oauth2::OAuth2StoreInitializer),
|
Box::new(initializers::oauth2::OAuth2StoreInitializer),
|
||||||
Box::new(initializers::oauth2_session::OAuth2SessionInitializer),
|
Box::new(initializers::oauth2_session::OAuth2SessionInitializer),
|
||||||
@@ -111,6 +112,7 @@ impl Hooks for App {
|
|||||||
.add_route(admin_discount_profiles::routes())
|
.add_route(admin_discount_profiles::routes())
|
||||||
.add_route(admin_categories::routes())
|
.add_route(admin_categories::routes())
|
||||||
.add_route(admin_orders::routes())
|
.add_route(admin_orders::routes())
|
||||||
|
.add_route(admin_payments::routes())
|
||||||
.add_route(admin_customers::routes())
|
.add_route(admin_customers::routes())
|
||||||
.add_route(admin_shipping::routes())
|
.add_route(admin_shipping::routes())
|
||||||
.add_route(admin_currencies::routes())
|
.add_route(admin_currencies::routes())
|
||||||
|
|||||||
@@ -335,6 +335,7 @@ async fn order_detail_page(
|
|||||||
.all(&ctx.db)
|
.all(&ctx.db)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let (bank_iban, bank_account_name) = settings::bank_details(&ctx).await?;
|
||||||
format::view(
|
format::view(
|
||||||
&v,
|
&v,
|
||||||
"account/order_detail.html",
|
"account/order_detail.html",
|
||||||
@@ -347,8 +348,8 @@ async fn order_detail_page(
|
|||||||
"customer_avatar": user.avatar_id,
|
"customer_avatar": user.avatar_id,
|
||||||
"order": order_view::detail(
|
"order": order_view::detail(
|
||||||
&order,
|
&order,
|
||||||
settings::get(&ctx, "bank_iban").unwrap_or(""),
|
&bank_iban,
|
||||||
settings::get(&ctx, "bank_account_name").unwrap_or(""),
|
&bank_account_name,
|
||||||
),
|
),
|
||||||
"items": order_view::items(&items),
|
"items": order_view::items(&items),
|
||||||
"lang": current_lang(&jar),
|
"lang": current_lang(&jar),
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ async fn render_show(
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let carrier = order_carrier(ctx, &order).await?;
|
let carrier = order_carrier(ctx, &order).await?;
|
||||||
|
let (bank_iban, bank_account_name) = settings::bank_details(ctx).await?;
|
||||||
// The order can be sent only if it maps to a real carrier and hasn't been
|
// The order can be sent only if it maps to a real carrier and hasn't been
|
||||||
// dispatched yet.
|
// dispatched yet.
|
||||||
let can_ship = carrier != "none" && order.tracking_number.is_none();
|
let can_ship = carrier != "none" && order.tracking_number.is_none();
|
||||||
@@ -103,8 +104,8 @@ async fn render_show(
|
|||||||
json!({
|
json!({
|
||||||
"order": view::detail(
|
"order": view::detail(
|
||||||
&order,
|
&order,
|
||||||
settings::get(ctx, "bank_iban").unwrap_or(""),
|
&bank_iban,
|
||||||
settings::get(ctx, "bank_account_name").unwrap_or(""),
|
&bank_account_name,
|
||||||
),
|
),
|
||||||
"items": view::items(&items),
|
"items": view::items(&items),
|
||||||
"statuses": ORDER_STATUSES,
|
"statuses": ORDER_STATUSES,
|
||||||
|
|||||||
112
src/controllers/admin_payments.rs
Normal file
112
src/controllers/admin_payments.rs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
//! Admin management for checkout payment methods and bank-transfer details.
|
||||||
|
|
||||||
|
use axum_extra::extract::cookie::CookieJar;
|
||||||
|
use loco_rs::prelude::*;
|
||||||
|
use sea_orm::{ActiveModelTrait, EntityTrait, QueryOrder, Set};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
controllers::i18n::current_lang,
|
||||||
|
models::{payment_methods, shop_settings},
|
||||||
|
shared::guard,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct PaymentMethodForm {
|
||||||
|
enabled: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct BankSettingsForm {
|
||||||
|
bank_account_name: String,
|
||||||
|
bank_iban: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_checked(value: &Option<String>) -> bool {
|
||||||
|
matches!(value.as_deref(), Some("on" | "true" | "1"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trimmed(value: &str) -> Option<String> {
|
||||||
|
let value = value.trim();
|
||||||
|
(!value.is_empty()).then(|| value.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
async fn index(
|
||||||
|
auth: auth::JWT,
|
||||||
|
jar: CookieJar,
|
||||||
|
ViewEngine(v): ViewEngine<TeraView>,
|
||||||
|
State(ctx): State<AppContext>,
|
||||||
|
) -> Result<Response> {
|
||||||
|
guard::current_admin(auth, &ctx).await?;
|
||||||
|
let methods = payment_methods::Entity::find()
|
||||||
|
.order_by_asc(payment_methods::Column::Position)
|
||||||
|
.all(&ctx.db)
|
||||||
|
.await?;
|
||||||
|
let rows: Vec<serde_json::Value> = methods
|
||||||
|
.iter()
|
||||||
|
.map(|m| {
|
||||||
|
json!({
|
||||||
|
"id": m.id,
|
||||||
|
"code": m.code,
|
||||||
|
"label_key": m.label_key(),
|
||||||
|
"enabled": m.enabled,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let bank_account_name = shop_settings::Entity::get(&ctx.db, "bank_account_name")
|
||||||
|
.await?
|
||||||
|
.unwrap_or_default();
|
||||||
|
let bank_iban = shop_settings::Entity::get(&ctx.db, "bank_iban")
|
||||||
|
.await?
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
format::view(
|
||||||
|
&v,
|
||||||
|
"admin/payments/index.html",
|
||||||
|
json!({
|
||||||
|
"methods": rows,
|
||||||
|
"bank_account_name": bank_account_name,
|
||||||
|
"bank_iban": bank_iban,
|
||||||
|
"lang": current_lang(&jar),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
async fn update_method(
|
||||||
|
auth: auth::JWT,
|
||||||
|
Path(id): Path<i32>,
|
||||||
|
State(ctx): State<AppContext>,
|
||||||
|
Form(form): Form<PaymentMethodForm>,
|
||||||
|
) -> Result<Response> {
|
||||||
|
guard::current_admin(auth, &ctx).await?;
|
||||||
|
let method = payment_methods::Entity::find_by_id(id)
|
||||||
|
.one(&ctx.db)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| Error::NotFound)?;
|
||||||
|
let mut active = method.into_active_model();
|
||||||
|
active.enabled = Set(is_checked(&form.enabled));
|
||||||
|
active.update(&ctx.db).await?;
|
||||||
|
format::redirect("/admin/payments")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
async fn update_bank(
|
||||||
|
auth: auth::JWT,
|
||||||
|
State(ctx): State<AppContext>,
|
||||||
|
Form(form): Form<BankSettingsForm>,
|
||||||
|
) -> Result<Response> {
|
||||||
|
guard::current_admin(auth, &ctx).await?;
|
||||||
|
shop_settings::Entity::set(&ctx.db, "bank_account_name", trimmed(&form.bank_account_name)).await?;
|
||||||
|
shop_settings::Entity::set(&ctx.db, "bank_iban", trimmed(&form.bank_iban)).await?;
|
||||||
|
format::redirect("/admin/payments")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn routes() -> Routes {
|
||||||
|
Routes::new()
|
||||||
|
.add("/admin/payments", get(index))
|
||||||
|
.add("/admin/payments/methods/{id}", post(update_method))
|
||||||
|
.add("/admin/payments/bank", post(update_bank))
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ use crate::{
|
|||||||
mailers::auth::AuthMailer,
|
mailers::auth::AuthMailer,
|
||||||
models::{
|
models::{
|
||||||
customer_profiles::{self, ProfileFields},
|
customer_profiles::{self, ProfileFields},
|
||||||
order_items, orders, shipping_methods,
|
order_items, orders, payment_methods, shipping_methods,
|
||||||
users::{self, normalize_account_type},
|
users::{self, normalize_account_type},
|
||||||
},
|
},
|
||||||
controllers::i18n::current_lang,
|
controllers::i18n::current_lang,
|
||||||
@@ -22,8 +22,6 @@ use crate::{
|
|||||||
views::checkout as view,
|
views::checkout as view,
|
||||||
};
|
};
|
||||||
|
|
||||||
const PAYMENT_METHODS: [&str; 2] = ["cod", "bank_transfer"];
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct CheckoutForm {
|
struct CheckoutForm {
|
||||||
email: String,
|
email: String,
|
||||||
@@ -76,6 +74,10 @@ async fn enabled_shipping_methods(ctx: &AppContext) -> Result<Vec<shipping_metho
|
|||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn enabled_payment_methods(ctx: &AppContext) -> Result<Vec<payment_methods::Model>> {
|
||||||
|
Ok(payment_methods::Entity::enabled(&ctx.db).await?)
|
||||||
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn checkout_page(
|
async fn checkout_page(
|
||||||
jar: CookieJar,
|
jar: CookieJar,
|
||||||
@@ -102,6 +104,16 @@ async fn checkout_page(
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
let payments: Vec<serde_json::Value> = enabled_payment_methods(&ctx)
|
||||||
|
.await?
|
||||||
|
.iter()
|
||||||
|
.map(|m| {
|
||||||
|
json!({
|
||||||
|
"code": m.code,
|
||||||
|
"label_key": m.label_key(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
// Prefill the form for a logged-in customer: contact name/email come from
|
// Prefill the form for a logged-in customer: contact name/email come from
|
||||||
// the user account, the address/phone from their saved profile (if any).
|
// the user account, the address/phone from their saved profile (if any).
|
||||||
@@ -130,6 +142,7 @@ async fn checkout_page(
|
|||||||
"subtotal": format_price(subtotal),
|
"subtotal": format_price(subtotal),
|
||||||
"subtotal_cents": subtotal,
|
"subtotal_cents": subtotal,
|
||||||
"shipping_methods": methods,
|
"shipping_methods": methods,
|
||||||
|
"payment_methods": payments,
|
||||||
"packeta_api_key": settings::get(&ctx, "packeta_api_key").unwrap_or(""),
|
"packeta_api_key": settings::get(&ctx, "packeta_api_key").unwrap_or(""),
|
||||||
"logged_in_admin": is_admin,
|
"logged_in_admin": is_admin,
|
||||||
"logged_in_customer": is_customer,
|
"logged_in_customer": is_customer,
|
||||||
@@ -239,7 +252,7 @@ async fn place_order(
|
|||||||
(None, None, None, None)
|
(None, None, None, None)
|
||||||
};
|
};
|
||||||
|
|
||||||
if !PAYMENT_METHODS.contains(&form.payment_method.as_str()) {
|
if payment_methods::Entity::find_enabled(&ctx.db, &form.payment_method).await?.is_none() {
|
||||||
return Err(Error::BadRequest("invalid payment method".to_string()));
|
return Err(Error::BadRequest("invalid payment method".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,14 +405,15 @@ async fn order_confirmation(
|
|||||||
let c = guard::chrome(&ctx, &jar).await;
|
let c = guard::chrome(&ctx, &jar).await;
|
||||||
let account_created = params.contains_key("account_created");
|
let account_created = params.contains_key("account_created");
|
||||||
|
|
||||||
|
let (bank_iban, bank_account_name) = settings::bank_details(&ctx).await?;
|
||||||
format::view(
|
format::view(
|
||||||
&v,
|
&v,
|
||||||
"shop/order_confirmed.html",
|
"shop/order_confirmed.html",
|
||||||
json!({
|
json!({
|
||||||
"order": view::detail(
|
"order": view::detail(
|
||||||
&order,
|
&order,
|
||||||
settings::get(&ctx, "bank_iban").unwrap_or(""),
|
&bank_iban,
|
||||||
settings::get(&ctx, "bank_account_name").unwrap_or(""),
|
&bank_account_name,
|
||||||
),
|
),
|
||||||
"items": view::items(&items),
|
"items": view::items(&items),
|
||||||
"logged_in_admin": c.logged_in_admin,
|
"logged_in_admin": c.logged_in_admin,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ pub mod admin_dashboard;
|
|||||||
pub mod admin_discount_profiles;
|
pub mod admin_discount_profiles;
|
||||||
pub mod admin_form;
|
pub mod admin_form;
|
||||||
pub mod admin_orders;
|
pub mod admin_orders;
|
||||||
|
pub mod admin_payments;
|
||||||
pub mod admin_products;
|
pub mod admin_products;
|
||||||
pub mod admin_shipping;
|
pub mod admin_shipping;
|
||||||
pub mod cart;
|
pub mod cart;
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ pub mod admin_seeder;
|
|||||||
pub mod currency_seeder;
|
pub mod currency_seeder;
|
||||||
pub mod oauth2;
|
pub mod oauth2;
|
||||||
pub mod oauth2_session;
|
pub mod oauth2_session;
|
||||||
|
pub mod payment_seeder;
|
||||||
pub mod shipping_seeder;
|
pub mod shipping_seeder;
|
||||||
pub mod view_engine;
|
pub mod view_engine;
|
||||||
|
|||||||
73
src/initializers/payment_seeder.rs
Normal file
73
src/initializers/payment_seeder.rs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
//! Ensures built-in payment methods and editable bank-transfer settings exist.
|
||||||
|
//!
|
||||||
|
//! Payment method enabled flags and bank account details are admin-managed in the
|
||||||
|
//! database. We seed missing rows only, so admin changes persist across restarts.
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use loco_rs::prelude::*;
|
||||||
|
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, Set};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
models::{payment_methods, shop_settings},
|
||||||
|
shared::settings,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// `(code, name, enabled, position)`
|
||||||
|
const METHODS: [(&str, &str, bool, i32); 2] = [
|
||||||
|
(payment_methods::COD, "Cash on delivery", true, 0),
|
||||||
|
(payment_methods::BANK_TRANSFER, "Bank transfer", true, 1),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub struct PaymentSeeder;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Initializer for PaymentSeeder {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"payment-seeder".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn before_run(&self, ctx: &AppContext) -> Result<()> {
|
||||||
|
for (code, name, enabled, position) in METHODS {
|
||||||
|
let exists = payment_methods::Entity::find()
|
||||||
|
.filter(payment_methods::Column::Code.eq(code))
|
||||||
|
.count(&ctx.db)
|
||||||
|
.await?
|
||||||
|
> 0;
|
||||||
|
if exists {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
payment_methods::ActiveModel {
|
||||||
|
code: Set(code.to_string()),
|
||||||
|
name: Set(name.to_string()),
|
||||||
|
enabled: Set(enabled),
|
||||||
|
position: Set(position),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.insert(&ctx.db)
|
||||||
|
.await?;
|
||||||
|
tracing::info!(payment = code, "seeded built-in payment method");
|
||||||
|
}
|
||||||
|
|
||||||
|
seed_setting(ctx, "bank_iban").await?;
|
||||||
|
seed_setting(ctx, "bank_account_name").await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn seed_setting(ctx: &AppContext, key: &str) -> Result<()> {
|
||||||
|
let exists = shop_settings::Entity::find()
|
||||||
|
.filter(shop_settings::Column::Key.eq(key))
|
||||||
|
.count(&ctx.db)
|
||||||
|
.await?
|
||||||
|
> 0;
|
||||||
|
if exists {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
shop_settings::ActiveModel {
|
||||||
|
key: Set(key.to_string()),
|
||||||
|
value: Set(settings::get(ctx, key).map(str::to_string)),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.insert(&ctx.db)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -15,10 +15,12 @@ pub mod discount_profiles;
|
|||||||
pub mod o_auth2_sessions;
|
pub mod o_auth2_sessions;
|
||||||
pub mod order_items;
|
pub mod order_items;
|
||||||
pub mod orders;
|
pub mod orders;
|
||||||
|
pub mod payment_methods;
|
||||||
pub mod product_images;
|
pub mod product_images;
|
||||||
pub mod product_product_tags;
|
pub mod product_product_tags;
|
||||||
pub mod product_tags;
|
pub mod product_tags;
|
||||||
pub mod product_variants;
|
pub mod product_variants;
|
||||||
pub mod products;
|
pub mod products;
|
||||||
pub mod shipping_methods;
|
pub mod shipping_methods;
|
||||||
|
pub mod shop_settings;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
|||||||
21
src/models/_entities/payment_methods.rs
Normal file
21
src/models/_entities/payment_methods.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||||
|
#[sea_orm(table_name = "payment_methods")]
|
||||||
|
pub struct Model {
|
||||||
|
pub created_at: DateTimeWithTimeZone,
|
||||||
|
pub updated_at: DateTimeWithTimeZone,
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: i32,
|
||||||
|
#[sea_orm(unique)]
|
||||||
|
pub code: String,
|
||||||
|
pub name: String,
|
||||||
|
pub enabled: bool,
|
||||||
|
pub position: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {}
|
||||||
@@ -13,10 +13,12 @@ pub use super::discount_profiles::Entity as DiscountProfiles;
|
|||||||
pub use super::o_auth2_sessions::Entity as OAuth2Sessions;
|
pub use super::o_auth2_sessions::Entity as OAuth2Sessions;
|
||||||
pub use super::order_items::Entity as OrderItems;
|
pub use super::order_items::Entity as OrderItems;
|
||||||
pub use super::orders::Entity as Orders;
|
pub use super::orders::Entity as Orders;
|
||||||
|
pub use super::payment_methods::Entity as PaymentMethods;
|
||||||
pub use super::product_images::Entity as ProductImages;
|
pub use super::product_images::Entity as ProductImages;
|
||||||
pub use super::product_product_tags::Entity as ProductProductTags;
|
pub use super::product_product_tags::Entity as ProductProductTags;
|
||||||
pub use super::product_tags::Entity as ProductTags;
|
pub use super::product_tags::Entity as ProductTags;
|
||||||
pub use super::product_variants::Entity as ProductVariants;
|
pub use super::product_variants::Entity as ProductVariants;
|
||||||
pub use super::products::Entity as Products;
|
pub use super::products::Entity as Products;
|
||||||
pub use super::shipping_methods::Entity as ShippingMethods;
|
pub use super::shipping_methods::Entity as ShippingMethods;
|
||||||
|
pub use super::shop_settings::Entity as ShopSettings;
|
||||||
pub use super::users::Entity as Users;
|
pub use super::users::Entity as Users;
|
||||||
|
|||||||
20
src/models/_entities/shop_settings.rs
Normal file
20
src/models/_entities/shop_settings.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||||
|
#[sea_orm(table_name = "shop_settings")]
|
||||||
|
pub struct Model {
|
||||||
|
pub created_at: DateTimeWithTimeZone,
|
||||||
|
pub updated_at: DateTimeWithTimeZone,
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: i32,
|
||||||
|
#[sea_orm(unique)]
|
||||||
|
pub key: String,
|
||||||
|
#[sea_orm(column_type = "Text", nullable)]
|
||||||
|
pub value: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {}
|
||||||
@@ -19,10 +19,12 @@ pub mod customer_profiles;
|
|||||||
pub mod o_auth2_sessions;
|
pub mod o_auth2_sessions;
|
||||||
pub mod order_items;
|
pub mod order_items;
|
||||||
pub mod orders;
|
pub mod orders;
|
||||||
|
pub mod payment_methods;
|
||||||
pub mod product_images;
|
pub mod product_images;
|
||||||
pub mod product_product_tags;
|
pub mod product_product_tags;
|
||||||
pub mod product_tags;
|
pub mod product_tags;
|
||||||
pub mod products;
|
pub mod products;
|
||||||
pub mod shipping_methods;
|
pub mod shipping_methods;
|
||||||
|
pub mod shop_settings;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
pub mod product_variants;
|
pub mod product_variants;
|
||||||
|
|||||||
54
src/models/payment_methods.rs
Normal file
54
src/models/payment_methods.rs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
use sea_orm::{ActiveValue, ColumnTrait, EntityTrait, QueryFilter, QueryOrder};
|
||||||
|
|
||||||
|
pub use crate::models::_entities::payment_methods::{ActiveModel, Column, Entity, Model};
|
||||||
|
pub type PaymentMethods = Entity;
|
||||||
|
|
||||||
|
pub const COD: &str = "cod";
|
||||||
|
pub const BANK_TRANSFER: &str = "bank_transfer";
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl ActiveModelBehavior for ActiveModel {
|
||||||
|
async fn before_save<C>(self, _db: &C, insert: bool) -> std::result::Result<Self, DbErr>
|
||||||
|
where
|
||||||
|
C: ConnectionTrait,
|
||||||
|
{
|
||||||
|
if !insert && self.updated_at.is_unchanged() {
|
||||||
|
let mut this = self;
|
||||||
|
this.updated_at = ActiveValue::set(chrono::Utc::now().into());
|
||||||
|
Ok(this)
|
||||||
|
} else {
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entity {
|
||||||
|
pub async fn enabled<C: ConnectionTrait>(db: &C) -> Result<Vec<Model>, DbErr> {
|
||||||
|
Entity::find()
|
||||||
|
.filter(Column::Enabled.eq(true))
|
||||||
|
.order_by_asc(Column::Position)
|
||||||
|
.all(db)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn find_enabled<C: ConnectionTrait>(db: &C, code: &str) -> Result<Option<Model>, DbErr> {
|
||||||
|
Entity::find()
|
||||||
|
.filter(Column::Code.eq(code))
|
||||||
|
.filter(Column::Enabled.eq(true))
|
||||||
|
.one(db)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Model {
|
||||||
|
pub fn label_key(&self) -> &'static str {
|
||||||
|
match self.code.as_str() {
|
||||||
|
COD => "payment-cod",
|
||||||
|
BANK_TRANSFER => "payment-bank",
|
||||||
|
_ => "payment-custom",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModel {}
|
||||||
47
src/models/shop_settings.rs
Normal file
47
src/models/shop_settings.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
use sea_orm::{ActiveValue, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, TryIntoModel};
|
||||||
|
|
||||||
|
pub use crate::models::_entities::shop_settings::{ActiveModel, Column, Entity, Model};
|
||||||
|
pub type ShopSettings = Entity;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl ActiveModelBehavior for ActiveModel {
|
||||||
|
async fn before_save<C>(self, _db: &C, insert: bool) -> std::result::Result<Self, DbErr>
|
||||||
|
where
|
||||||
|
C: ConnectionTrait,
|
||||||
|
{
|
||||||
|
if !insert && self.updated_at.is_unchanged() {
|
||||||
|
let mut this = self;
|
||||||
|
this.updated_at = ActiveValue::set(chrono::Utc::now().into());
|
||||||
|
Ok(this)
|
||||||
|
} else {
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entity {
|
||||||
|
pub async fn get<C: ConnectionTrait>(db: &C, key: &str) -> Result<Option<String>, DbErr> {
|
||||||
|
Ok(Entity::find()
|
||||||
|
.filter(Column::Key.eq(key))
|
||||||
|
.one(db)
|
||||||
|
.await?
|
||||||
|
.and_then(|setting| setting.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set<C: ConnectionTrait>(db: &C, key: &str, value: Option<String>) -> Result<Model, DbErr> {
|
||||||
|
let mut active = match Entity::find()
|
||||||
|
.filter(Column::Key.eq(key))
|
||||||
|
.one(db)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Some(existing) => existing.into_active_model(),
|
||||||
|
None => ActiveModel {
|
||||||
|
key: ActiveValue::set(key.to_string()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
active.value = ActiveValue::set(value);
|
||||||
|
active.save(db).await?.try_into_model()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
use loco_rs::prelude::*;
|
use loco_rs::prelude::*;
|
||||||
|
|
||||||
|
use crate::models::shop_settings;
|
||||||
|
|
||||||
/// Look up a string-valued `settings.<key>` entry, returning `None` if config
|
/// Look up a string-valued `settings.<key>` entry, returning `None` if config
|
||||||
/// has no settings map, the key is missing, or the value is not a string.
|
/// has no settings map, the key is missing, or the value is not a string.
|
||||||
pub fn get<'a>(ctx: &'a AppContext, key: &str) -> Option<&'a str> {
|
pub fn get<'a>(ctx: &'a AppContext, key: &str) -> Option<&'a str> {
|
||||||
@@ -11,3 +13,20 @@ pub fn get<'a>(ctx: &'a AppContext, key: &str) -> Option<&'a str> {
|
|||||||
.and_then(|settings| settings.get(key))
|
.and_then(|settings| settings.get(key))
|
||||||
.and_then(|value| value.as_str())
|
.and_then(|value| value.as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Look up an admin-editable setting in the database, falling back to config when
|
||||||
|
/// the row is missing. Empty DB values are returned as-is so admins can clear a
|
||||||
|
/// setting deliberately.
|
||||||
|
pub async fn get_editable(ctx: &AppContext, key: &str) -> Result<String> {
|
||||||
|
Ok(match shop_settings::Entity::get(&ctx.db, key).await? {
|
||||||
|
Some(value) => value,
|
||||||
|
None => get(ctx, key).unwrap_or("").to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn bank_details(ctx: &AppContext) -> Result<(String, String)> {
|
||||||
|
Ok((
|
||||||
|
get_editable(ctx, "bank_iban").await?,
|
||||||
|
get_editable(ctx, "bank_account_name").await?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user