Implement org credit line debt and bank repayment flow #2
@ -35,12 +35,6 @@ PREP_RECOMPILE_END;
|
||||
GVAR(BankStore) call ["deposit", [_uid, _amount]];
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestPayment), {
|
||||
params [["_uid", "", [""]], ["_amount", 0, [0]]];
|
||||
|
||||
GVAR(BankStore) call ["payment", [_uid, _amount]];
|
||||
}] call CFUNC(addEventHandler);
|
||||
|
||||
[QGVAR(requestSubmitPin), {
|
||||
params [["_uid", "", [""]], ["_pin", "", [""]]];
|
||||
|
||||
|
||||
@ -37,6 +37,14 @@ GVAR(BankPayloadBuilder) = createHashMapObject [[
|
||||
_context set ["fromField", _from];
|
||||
_context
|
||||
}],
|
||||
["buildCheckoutContext", compileFinal {
|
||||
params [["_source", "bank", [""]], ["_commit", false, [false]]];
|
||||
|
||||
createHashMapFromArray [
|
||||
["commit", _commit],
|
||||
["sourceField", toLowerANSI _source]
|
||||
]
|
||||
}],
|
||||
["resolveOrgState", compileFinal {
|
||||
params [["_uid", "", [""]]];
|
||||
|
||||
|
||||
@ -145,35 +145,23 @@ GVAR(BankBaseStore) = compileFinal createHashMapFromArray [
|
||||
params [["_uid", "", [""]], ["_source", "cash", [""]], ["_amount", 0, [0]], ["_commit", false, [false]]];
|
||||
|
||||
private _result = createHashMapFromArray [["success", false], ["message", "Unable to process bank payment."], ["patch", createHashMap]];
|
||||
private _field = switch (toLowerANSI _source) do {
|
||||
case "cash": { "cash" };
|
||||
case "bank": { "bank" };
|
||||
default { "" };
|
||||
};
|
||||
if (_uid isEqualTo "") exitWith { _result };
|
||||
|
||||
if (_field isEqualTo "") exitWith {
|
||||
_result set ["message", "Selected bank payment source is unsupported."];
|
||||
private _checkoutContext = GVAR(BankPayloadBuilder) call ["buildCheckoutContext", [_source, _commit]];
|
||||
private _envelope = _self call [
|
||||
"callHotBankEnvelope",
|
||||
[
|
||||
"bank:hot:charge_checkout",
|
||||
[_uid, str _amount, toJSON _checkoutContext]
|
||||
]
|
||||
];
|
||||
private _mutationResult = _envelope getOrDefault ["data", createHashMap];
|
||||
private _patch = _self call ["finalizeMutation", [_uid, _mutationResult, false]];
|
||||
if (_patch isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", _envelope getOrDefault ["error", "Bank checkout payment failed."]];
|
||||
_result
|
||||
};
|
||||
|
||||
private _account = _self call ["get", [_uid, ""]];
|
||||
if (_account isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Bank account data is unavailable for checkout."];
|
||||
_result
|
||||
};
|
||||
|
||||
private _balance = _account getOrDefault [_field, 0];
|
||||
if (_balance < _amount) exitWith {
|
||||
_result set ["message", ["Bank balance cannot cover this checkout.", "Cash on hand cannot cover this checkout."] select (_field isEqualTo "cash")];
|
||||
_result
|
||||
};
|
||||
|
||||
private _patch = createHashMapFromArray [[_field, (_balance - _amount)]];
|
||||
if (_commit) then {
|
||||
private _result = _self call ["callHotBank", ["bank:hot:patch", [_uid, toJSON _patch]]];
|
||||
_patch = _self call ["finalizeMutation", [_uid, _result, false]];
|
||||
};
|
||||
|
||||
_result set ["success", true];
|
||||
_result set ["message", ""];
|
||||
_result set ["patch", _patch];
|
||||
@ -214,17 +202,19 @@ GVAR(BankBaseStore) = compileFinal createHashMapFromArray [
|
||||
private _playerName = if (isNull _player) then { "Unknown" } else { name _player };
|
||||
["bank:exists", [_uid]] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
||||
if !(_isSuccess) exitWith {
|
||||
["ERROR", format ["Failed to check if bank account %1 exists! Using fallback account.", _uid]] call EFUNC(common,log);
|
||||
|
||||
private _fallbackAccount = GVAR(BankModel) call ["fromPlayer", [_player]];
|
||||
_fallbackAccount set ["uid", _uid];
|
||||
if ((_fallbackAccount getOrDefault ["name", ""]) isEqualTo "") then {
|
||||
_fallbackAccount set ["name", _playerName];
|
||||
};
|
||||
|
||||
_fallbackAccount = _self call ["normalizeAccount", [_uid, _fallbackAccount, _playerName]];
|
||||
GVAR(BankMessenger) call ["sendAccountSync", [_uid, _fallbackAccount, CRPC(bank,responseInitBank)]];
|
||||
_fallbackAccount
|
||||
["ERROR", format ["Failed to check if bank account %1 exists in backend.", _uid]] call EFUNC(common,log);
|
||||
GVAR(BankMessenger) call ["sendAlert", [_uid, "error", "Bank backend is unavailable right now."]];
|
||||
createHashMap
|
||||
};
|
||||
if !(_result isEqualType "") exitWith {
|
||||
["ERROR", format ["Bank exists check for %1 returned an invalid response.", _uid]] call EFUNC(common,log);
|
||||
GVAR(BankMessenger) call ["sendAlert", [_uid, "error", "Bank backend returned an invalid response."]];
|
||||
createHashMap
|
||||
};
|
||||
if ((_result find "Error:") == 0) exitWith {
|
||||
["ERROR", format ["Bank exists check for %1 failed: %2", _uid, _result]] call EFUNC(common,log);
|
||||
GVAR(BankMessenger) call ["sendAlert", [_uid, "error", _result select [7]]];
|
||||
createHashMap
|
||||
};
|
||||
|
||||
private _finalAccount = createHashMap;
|
||||
@ -241,23 +231,29 @@ GVAR(BankBaseStore) = compileFinal createHashMapFromArray [
|
||||
private _json = _self call ["toJSON", [_finalAccount]];
|
||||
["bank:create", [_uid, _json]] call EFUNC(extension,extCall) params ["_createResult", "_createSuccess"];
|
||||
if (!_createSuccess) exitWith {
|
||||
["ERROR", format ["Failed to create bank account %1! Using fallback account.", _uid]] call EFUNC(common,log);
|
||||
|
||||
_finalAccount = _self call ["normalizeAccount", [_uid, _finalAccount, _playerName]];
|
||||
GVAR(BankMessenger) call ["sendAccountSync", [_uid, _finalAccount, CRPC(bank,responseInitBank)]];
|
||||
_finalAccount
|
||||
["ERROR", format ["Failed to create bank account %1 in backend.", _uid]] call EFUNC(common,log);
|
||||
GVAR(BankMessenger) call ["sendAlert", [_uid, "error", "Failed to create bank account in backend."]];
|
||||
createHashMap
|
||||
};
|
||||
if !(_createResult isEqualType "") exitWith {
|
||||
["ERROR", format ["Bank create for %1 returned an invalid response.", _uid]] call EFUNC(common,log);
|
||||
GVAR(BankMessenger) call ["sendAlert", [_uid, "error", "Bank backend returned an invalid create response."]];
|
||||
createHashMap
|
||||
};
|
||||
if ((_createResult find "Error:") == 0) exitWith {
|
||||
["ERROR", format ["Bank create for %1 failed: %2", _uid, _createResult]] call EFUNC(common,log);
|
||||
GVAR(BankMessenger) call ["sendAlert", [_uid, "error", _createResult select [7]]];
|
||||
createHashMap
|
||||
};
|
||||
|
||||
_finalAccount = _self call ["loadHotBank", [_uid, true, _playerName]];
|
||||
["INFO", format ["Created new bank account for %1", _uid]] call EFUNC(common,log);
|
||||
};
|
||||
|
||||
if (_finalAccount isEqualTo createHashMap) then {
|
||||
_finalAccount = GVAR(BankModel) call ["fromPlayer", [_player]];
|
||||
_finalAccount set ["uid", _uid];
|
||||
if ((_finalAccount getOrDefault ["name", ""]) isEqualTo "") then {
|
||||
_finalAccount set ["name", _playerName];
|
||||
};
|
||||
if (_finalAccount isEqualTo createHashMap) exitWith {
|
||||
["ERROR", format ["Failed to initialize bank hot state for %1.", _uid]] call EFUNC(common,log);
|
||||
GVAR(BankMessenger) call ["sendAlert", [_uid, "error", "Bank account hot state could not be initialized."]];
|
||||
createHashMap
|
||||
};
|
||||
|
||||
_finalAccount = _self call ["normalizeAccount", [_uid, _finalAccount, _playerName]];
|
||||
@ -294,25 +290,17 @@ GVAR(BankBaseStore) = compileFinal createHashMapFromArray [
|
||||
params [["_uid", "", [""]]];
|
||||
if (_uid isEqualTo "") exitWith { createHashMap };
|
||||
|
||||
private _account = _self call ["callHotBank", ["bank:hot:save", [_uid]]];
|
||||
if (_account isEqualTo createHashMap) exitWith { _account };
|
||||
private _envelope = _self call ["callHotBankEnvelope", ["bank:hot:save", [_uid]]];
|
||||
private _account = _envelope getOrDefault ["data", createHashMap];
|
||||
if (_account isEqualTo createHashMap) exitWith {
|
||||
private _message = _envelope getOrDefault ["error", "Bank save failed."];
|
||||
["ERROR", format ["Failed to save bank account %1: %2", _uid, _message]] call EFUNC(common,log);
|
||||
GVAR(BankMessenger) call ["sendAlert", [_uid, "error", _message]];
|
||||
createHashMap
|
||||
};
|
||||
|
||||
_self call ["normalizeAccount", [_uid, _account, ""]]
|
||||
}],
|
||||
["payment", compileFinal {
|
||||
params [["_uid", "", [""]], ["_amount", 0, [0]]];
|
||||
|
||||
_self call [
|
||||
"runMutation",
|
||||
[
|
||||
_uid,
|
||||
"bank:hot:payment",
|
||||
[_uid, str _amount],
|
||||
false,
|
||||
format ["Paid $%1", [_amount] call EFUNC(common,formatNumber)]
|
||||
]
|
||||
]
|
||||
}],
|
||||
["transfer", compileFinal {
|
||||
params [["_uid", "", [""]], ["_target", "", [""]], ["_amount", 0, [0]], ["_context", createHashMap, [createHashMap]]];
|
||||
|
||||
@ -325,7 +313,13 @@ GVAR(BankBaseStore) = compileFinal createHashMapFromArray [
|
||||
]
|
||||
];
|
||||
private _result = _envelope getOrDefault ["data", createHashMap];
|
||||
if (_result isEqualTo createHashMap) exitWith { false };
|
||||
if (_result isEqualTo createHashMap) exitWith {
|
||||
private _message = _envelope getOrDefault ["error", "Bank transfer failed."];
|
||||
if (_message isNotEqualTo "") then {
|
||||
GVAR(BankMessenger) call ["sendAlert", [_uid, "error", _message]];
|
||||
};
|
||||
false
|
||||
};
|
||||
|
||||
private _sourceAccount = _result getOrDefault ["sourceAccount", createHashMap];
|
||||
private _targetAccount = _result getOrDefault ["targetAccount", createHashMap];
|
||||
|
||||
@ -9,6 +9,7 @@ use forge_services::{ActorHotStateService, ActorService};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use crate::adapters::ExtensionRedisClient;
|
||||
use crate::enqueue_persistence_task;
|
||||
use crate::helpers::resolve_uid;
|
||||
use crate::log::log;
|
||||
|
||||
@ -104,8 +105,13 @@ pub(crate) fn save_hot_actor(call_context: CallContext, key: String) -> String {
|
||||
None => return format!("Error: Failed to resolve UID for key: {}", key),
|
||||
};
|
||||
|
||||
match HOT_ACTOR_SERVICE.save_actor(resolved_uid) {
|
||||
Ok(saved_actor) => serialize_hot_actor(saved_actor),
|
||||
match HOT_ACTOR_SERVICE.get_actor(resolved_uid.clone()) {
|
||||
Ok(actor) => {
|
||||
enqueue_persistence_task("actor", move || {
|
||||
HOT_ACTOR_SERVICE.save_actor(resolved_uid).map(|_| ())
|
||||
});
|
||||
serialize_hot_actor(actor)
|
||||
}
|
||||
Err(error) => format!("Error: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,14 +5,15 @@
|
||||
|
||||
use arma_rs::{CallContext, Group};
|
||||
use forge_models::{
|
||||
BankMutationResult, BankOperationContext, BankPinContext, BankTransferContext,
|
||||
BankTransferResult,
|
||||
BankCheckoutContext, BankMutationResult, BankOperationContext, BankPinContext,
|
||||
BankTransferContext, BankTransferResult,
|
||||
};
|
||||
use forge_repositories::{InMemoryBankHotRepository, RedisBankRepository};
|
||||
use forge_services::{BankHotStateService, BankService};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use crate::adapters::ExtensionRedisClient;
|
||||
use crate::enqueue_persistence_task;
|
||||
use crate::helpers::resolve_uid;
|
||||
use crate::log::log;
|
||||
|
||||
@ -51,9 +52,9 @@ pub fn group() -> Group {
|
||||
.command("get", get_hot_bank)
|
||||
.command("override", override_hot_bank)
|
||||
.command("patch", patch_hot_bank)
|
||||
.command("charge_checkout", charge_checkout_hot_bank)
|
||||
.command("deposit", deposit_hot_bank)
|
||||
.command("withdraw", withdraw_hot_bank)
|
||||
.command("payment", payment_hot_bank)
|
||||
.command("deposit_earnings", deposit_earnings_hot_bank)
|
||||
.command("transfer", transfer_hot_bank)
|
||||
.command("validate_pin", validate_pin_hot_bank)
|
||||
@ -99,6 +100,11 @@ fn parse_transfer_context(json_context: String) -> Result<BankTransferContext, S
|
||||
.map_err(|error| format!("Invalid bank transfer context: {}", error))
|
||||
}
|
||||
|
||||
fn parse_checkout_context(json_context: String) -> Result<BankCheckoutContext, String> {
|
||||
serde_json::from_str(&json_context)
|
||||
.map_err(|error| format!("Invalid bank checkout context: {}", error))
|
||||
}
|
||||
|
||||
fn parse_pin_context(json_context: String) -> Result<BankPinContext, String> {
|
||||
serde_json::from_str(&json_context)
|
||||
.map_err(|error| format!("Invalid bank PIN context: {}", error))
|
||||
@ -156,6 +162,32 @@ pub(crate) fn patch_hot_bank(call_context: CallContext, key: String, json_patch:
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn charge_checkout_hot_bank(
|
||||
call_context: CallContext,
|
||||
key: String,
|
||||
amount: String,
|
||||
json_context: String,
|
||||
) -> String {
|
||||
let resolved_uid = match resolve_uid(&key, &call_context) {
|
||||
Some(uid) => uid,
|
||||
None => return format!("Error: Failed to resolve UID for key: {}", key),
|
||||
};
|
||||
|
||||
let amount = match parse_amount(amount, "checkout") {
|
||||
Ok(value) => value,
|
||||
Err(error) => return format!("Error: {}", error),
|
||||
};
|
||||
let context = match parse_checkout_context(json_context) {
|
||||
Ok(value) => value,
|
||||
Err(error) => return format!("Error: {}", error),
|
||||
};
|
||||
|
||||
match HOT_BANK_SERVICE.charge_checkout(resolved_uid, amount, context) {
|
||||
Ok(result) => serialize_hot_bank_mutation(result),
|
||||
Err(error) => format!("Error: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn deposit_hot_bank(
|
||||
call_context: CallContext,
|
||||
key: String,
|
||||
@ -208,23 +240,6 @@ pub(crate) fn withdraw_hot_bank(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn payment_hot_bank(call_context: CallContext, key: String, amount: String) -> String {
|
||||
let resolved_uid = match resolve_uid(&key, &call_context) {
|
||||
Some(uid) => uid,
|
||||
None => return format!("Error: Failed to resolve UID for key: {}", key),
|
||||
};
|
||||
|
||||
let amount = match parse_amount(amount, "payment") {
|
||||
Ok(value) => value,
|
||||
Err(error) => return format!("Error: {}", error),
|
||||
};
|
||||
|
||||
match HOT_BANK_SERVICE.payment(resolved_uid, amount) {
|
||||
Ok(result) => serialize_hot_bank_mutation(result),
|
||||
Err(error) => format!("Error: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn deposit_earnings_hot_bank(
|
||||
call_context: CallContext,
|
||||
key: String,
|
||||
@ -308,8 +323,13 @@ pub(crate) fn save_hot_bank(call_context: CallContext, key: String) -> String {
|
||||
None => return format!("Error: Failed to resolve UID for key: {}", key),
|
||||
};
|
||||
|
||||
match HOT_BANK_SERVICE.save_bank(resolved_uid) {
|
||||
Ok(saved_bank) => serialize_hot_bank(saved_bank),
|
||||
match HOT_BANK_SERVICE.get_bank(resolved_uid.clone()) {
|
||||
Ok(bank) => {
|
||||
enqueue_persistence_task("bank", move || {
|
||||
HOT_BANK_SERVICE.save_bank(resolved_uid).map(|_| ())
|
||||
});
|
||||
serialize_hot_bank(bank)
|
||||
}
|
||||
Err(error) => format!("Error: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ use std::collections::HashMap;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use crate::adapters::ExtensionRedisClient;
|
||||
use crate::enqueue_persistence_task;
|
||||
use crate::helpers::resolve_uid;
|
||||
use crate::log::log;
|
||||
|
||||
@ -105,8 +106,13 @@ pub(crate) fn save_hot_garage(call_context: CallContext, key: String) -> String
|
||||
None => return format!("Error: Failed to resolve UID for key: {}", key),
|
||||
};
|
||||
|
||||
match HOT_GARAGE_SERVICE.save_garage(resolved_uid) {
|
||||
Ok(saved_garage) => serialize_hot_vehicles(saved_garage),
|
||||
match HOT_GARAGE_SERVICE.get_garage(resolved_uid.clone()) {
|
||||
Ok(garage) => {
|
||||
enqueue_persistence_task("garage", move || {
|
||||
HOT_GARAGE_SERVICE.save_garage(resolved_uid).map(|_| ())
|
||||
});
|
||||
serialize_hot_vehicles(garage)
|
||||
}
|
||||
Err(error) => format!("Error: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ static CONTEXT: LazyLock<TokioRwLock<Option<Context>>> = LazyLock::new(|| TokioR
|
||||
static REDIS_POOL: OnceLock<redis::client::RedisClient> = OnceLock::new();
|
||||
/// Global multi-threaded Tokio runtime used to execute async operations from
|
||||
/// command handlers and startup tasks.
|
||||
static RUNTIME: LazyLock<Runtime> = LazyLock::new(|| {
|
||||
pub(crate) static RUNTIME: LazyLock<Runtime> = LazyLock::new(|| {
|
||||
Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
@ -54,6 +54,21 @@ enum ConnectionState {
|
||||
static CONNECTION_STATE: LazyLock<StdRwLock<ConnectionState>> =
|
||||
LazyLock::new(|| StdRwLock::new(ConnectionState::Initializing));
|
||||
|
||||
pub(crate) fn enqueue_persistence_task<F>(module: &'static str, job: F)
|
||||
where
|
||||
F: FnOnce() -> Result<(), String> + Send + 'static,
|
||||
{
|
||||
RUNTIME.spawn_blocking(move || {
|
||||
if let Err(error) = job() {
|
||||
crate::log::log(
|
||||
module,
|
||||
"ERROR",
|
||||
&format!("Async persistence failed: {}", error),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[arma]
|
||||
/// Initializes the extension, registers commands/groups, and asynchronously
|
||||
/// creates the Redis connection pool on the global runtime.
|
||||
|
||||
@ -6,6 +6,7 @@ use std::collections::HashMap;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use crate::adapters::ExtensionRedisClient;
|
||||
use crate::enqueue_persistence_task;
|
||||
use crate::helpers::resolve_uid;
|
||||
use crate::log::log;
|
||||
|
||||
@ -98,8 +99,13 @@ pub(crate) fn save_hot_locker(call_context: CallContext, key: String) -> String
|
||||
None => return format!("Error: Failed to resolve UID for key: {}", key),
|
||||
};
|
||||
|
||||
match HOT_LOCKER_SERVICE.save_locker(resolved_uid) {
|
||||
Ok(saved_locker) => serialize_hot_items(saved_locker),
|
||||
match HOT_LOCKER_SERVICE.get_locker(resolved_uid.clone()) {
|
||||
Ok(locker) => {
|
||||
enqueue_persistence_task("locker", move || {
|
||||
HOT_LOCKER_SERVICE.save_locker(resolved_uid).map(|_| ())
|
||||
});
|
||||
serialize_hot_items(locker)
|
||||
}
|
||||
Err(error) => format!("Error: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ use forge_services::{OrgHotStateService, OrgService};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use crate::adapters::ExtensionRedisClient;
|
||||
use crate::enqueue_persistence_task;
|
||||
use crate::log::log;
|
||||
|
||||
/// Global organization service instance.
|
||||
@ -104,8 +105,11 @@ pub(crate) fn override_hot_org(org_id: String, json_data: String) -> String {
|
||||
}
|
||||
|
||||
pub(crate) fn save_hot_org(org_id: String) -> String {
|
||||
match HOT_ORG_SERVICE.save_org(org_id) {
|
||||
Ok(org) => serialize_hot_org(org),
|
||||
match HOT_ORG_SERVICE.get_org(org_id.clone()) {
|
||||
Ok(org) => {
|
||||
enqueue_persistence_task("org", move || HOT_ORG_SERVICE.save_org(org_id).map(|_| ()));
|
||||
serialize_hot_org(org)
|
||||
}
|
||||
Err(error) => format!("Error: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
@ -253,6 +253,15 @@ fn route_command(
|
||||
arguments[1].clone(),
|
||||
))
|
||||
}
|
||||
"bank:hot:charge_checkout" => {
|
||||
expect_arg_count(function_name, &arguments, 3)?;
|
||||
Ok(bank::charge_checkout_hot_bank(
|
||||
call_context,
|
||||
arguments[0].clone(),
|
||||
arguments[1].clone(),
|
||||
arguments[2].clone(),
|
||||
))
|
||||
}
|
||||
"bank:hot:deposit" => {
|
||||
expect_arg_count(function_name, &arguments, 3)?;
|
||||
Ok(bank::deposit_hot_bank(
|
||||
@ -271,14 +280,6 @@ fn route_command(
|
||||
arguments[2].clone(),
|
||||
))
|
||||
}
|
||||
"bank:hot:payment" => {
|
||||
expect_arg_count(function_name, &arguments, 2)?;
|
||||
Ok(bank::payment_hot_bank(
|
||||
call_context,
|
||||
arguments[0].clone(),
|
||||
arguments[1].clone(),
|
||||
))
|
||||
}
|
||||
"bank:hot:deposit_earnings" => {
|
||||
expect_arg_count(function_name, &arguments, 3)?;
|
||||
Ok(bank::deposit_earnings_hot_bank(
|
||||
|
||||
@ -5,6 +5,7 @@ use forge_services::{VGarageHotStateService, VGarageService};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use crate::adapters::ExtensionRedisClient;
|
||||
use crate::enqueue_persistence_task;
|
||||
use crate::helpers::resolve_uid;
|
||||
use crate::log::log;
|
||||
|
||||
@ -121,8 +122,13 @@ pub(crate) fn save_hot_vgarage(call_context: CallContext, key: String) -> String
|
||||
None => return format!("Error: Failed to resolve UID for key: {}", key),
|
||||
};
|
||||
|
||||
match HOT_VGARAGE_SERVICE.save_garage(&resolved_uid) {
|
||||
Ok(garage) => serialize_hot_vgarage(garage),
|
||||
match HOT_VGARAGE_SERVICE.fetch_garage(&resolved_uid) {
|
||||
Ok(garage) => {
|
||||
enqueue_persistence_task("owned_garage", move || {
|
||||
HOT_VGARAGE_SERVICE.save_garage(&resolved_uid).map(|_| ())
|
||||
});
|
||||
serialize_hot_vgarage(garage)
|
||||
}
|
||||
Err(error) => format!("Error: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ use forge_services::{VLockerHotStateService, VLockerService};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use crate::adapters::ExtensionRedisClient;
|
||||
use crate::enqueue_persistence_task;
|
||||
use crate::helpers::resolve_uid;
|
||||
use crate::log::log;
|
||||
|
||||
@ -120,8 +121,13 @@ pub(crate) fn save_hot_vlocker(call_context: CallContext, key: String) -> String
|
||||
None => return format!("Error: Failed to resolve UID for key: {}", key),
|
||||
};
|
||||
|
||||
match HOT_VLOCKER_SERVICE.save_locker(&resolved_uid) {
|
||||
Ok(locker) => serialize_hot_vlocker(locker),
|
||||
match HOT_VLOCKER_SERVICE.fetch_locker(&resolved_uid) {
|
||||
Ok(locker) => {
|
||||
enqueue_persistence_task("owned_locker", move || {
|
||||
HOT_VLOCKER_SERVICE.save_locker(&resolved_uid).map(|_| ())
|
||||
});
|
||||
serialize_hot_vlocker(locker)
|
||||
}
|
||||
Err(error) => format!("Error: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,6 +45,13 @@ pub struct BankTransferContext {
|
||||
pub from_field: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BankCheckoutContext {
|
||||
pub source_field: String,
|
||||
pub commit: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BankPinContext {
|
||||
|
||||
@ -9,8 +9,8 @@ pub mod v_locker;
|
||||
|
||||
pub use actor::Actor;
|
||||
pub use bank::{
|
||||
Bank, BankMutationResult, BankOperationContext, BankPinContext, BankTransferContext,
|
||||
BankTransferResult,
|
||||
Bank, BankCheckoutContext, BankMutationResult, BankOperationContext, BankPinContext,
|
||||
BankTransferContext, BankTransferResult,
|
||||
};
|
||||
pub use cad::{
|
||||
CadActivityEntry, CadAssignmentMutationResult, CadDispatchOrderContextSeed,
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
//! For full documentation, architecture, and examples, see the [crate README](../README.md).
|
||||
|
||||
use forge_models::{
|
||||
Bank, BankMutationResult, BankOperationContext, BankPinContext, BankTransferContext,
|
||||
BankTransferResult,
|
||||
Bank, BankCheckoutContext, BankMutationResult, BankOperationContext, BankPinContext,
|
||||
BankTransferContext, BankTransferResult,
|
||||
};
|
||||
use forge_repositories::{BankHotRepository, BankRepository};
|
||||
use serde_json::{Value, json};
|
||||
@ -96,6 +96,51 @@ impl<R: BankRepository, H: BankHotRepository> BankHotStateService<R, H> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn charge_checkout(
|
||||
&self,
|
||||
key: String,
|
||||
amount: f64,
|
||||
context: BankCheckoutContext,
|
||||
) -> Result<BankMutationResult, String> {
|
||||
if amount <= 0.0 {
|
||||
return Err("Checkout amount must be greater than zero".to_string());
|
||||
}
|
||||
|
||||
let mut bank = self.get_bank(key)?;
|
||||
let source_field = match context.source_field.trim().to_ascii_lowercase().as_str() {
|
||||
"cash" => "cash",
|
||||
"bank" => "bank",
|
||||
_ => return Err("Selected bank payment source is unsupported.".to_string()),
|
||||
};
|
||||
|
||||
let source_balance = match source_field {
|
||||
"cash" => bank.cash,
|
||||
_ => bank.bank,
|
||||
};
|
||||
if source_balance < amount {
|
||||
return Err(match source_field {
|
||||
"cash" => "Cash on hand cannot cover this checkout.".to_string(),
|
||||
_ => "Bank balance cannot cover this checkout.".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
match source_field {
|
||||
"cash" => bank.cash -= amount,
|
||||
_ => bank.bank -= amount,
|
||||
}
|
||||
|
||||
bank.validate()
|
||||
.map_err(|e| format!("Validation failed: {}", e))?;
|
||||
if context.commit {
|
||||
self.repository.save(&bank)?;
|
||||
}
|
||||
|
||||
Ok(BankMutationResult {
|
||||
account: bank.clone(),
|
||||
patch: build_patch(&bank, &[source_field])?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn deposit(
|
||||
&self,
|
||||
key: String,
|
||||
@ -152,23 +197,6 @@ impl<R: BankRepository, H: BankHotRepository> BankHotStateService<R, H> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn payment(&self, key: String, amount: f64) -> Result<BankMutationResult, String> {
|
||||
if amount <= 0.0 {
|
||||
return Err("Payment amount must be greater than zero".to_string());
|
||||
}
|
||||
|
||||
let mut bank = self.get_bank(key)?;
|
||||
bank.bank += amount;
|
||||
bank.validate()
|
||||
.map_err(|e| format!("Validation failed: {}", e))?;
|
||||
self.repository.save(&bank)?;
|
||||
|
||||
Ok(BankMutationResult {
|
||||
account: bank.clone(),
|
||||
patch: build_patch(&bank, &["bank"])?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn deposit_earnings(
|
||||
&self,
|
||||
key: String,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user