diff --git a/code/modules/economy/ATM.dm b/code/modules/economy/ATM.dm index b094dded37e..fa07c0c4085 100644 --- a/code/modules/economy/ATM.dm +++ b/code/modules/economy/ATM.dm @@ -7,11 +7,12 @@ log transactions */ -#define NO_SCREEN 0 +#define DEFAULT_SCREEN 0 #define CHANGE_SECURITY_LEVEL 1 #define TRANSFER_FUNDS 2 #define VIEW_TRANSACTION_LOGS 3 #define PRINT_DELAY 100 +#define LOCKOUT_TIME 120 /obj/machinery/atm name = "Nanotrasen automatic teller machine" @@ -31,7 +32,7 @@ log transactions var/machine_id = "" var/obj/item/card/held_card var/editing_security_level = 0 - var/view_screen = NO_SCREEN + var/view_screen = DEFAULT_SCREEN var/lastprint = 0 // Printer needs time to cooldown /obj/machinery/atm/New() @@ -97,7 +98,7 @@ log transactions if(!powered()) return var/obj/item/stack/spacecash/C = I - playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 50, 1) + playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 50, TRUE) authenticated_account.credit(C.amount, "Credit deposit", machine_id, authenticated_account.owner_name) @@ -115,21 +116,19 @@ log transactions return if(!linked_db) reconnect_database() - ui_interact(user) + tgui_interact(user) /obj/machinery/atm/attack_ghost(mob/user) - ui_interact(user) + tgui_interact(user) -/obj/machinery/atm/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - user.set_machine(src) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/atm/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if (!ui) - ui = new(user, src, ui_key, "atm.tmpl", name, 550, 650) + ui = new(user, src, ui_key, "ATM", name, 550, 650) ui.open() -/obj/machinery/atm/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state) - var/data[0] - data["src"] = UID() +/obj/machinery/atm/tgui_data(mob/user) + var/list/data = list() data["view_screen"] = view_screen data["machine_id"] = machine_id data["held_card_name"] = held_card ? held_card.name : "------" @@ -155,139 +154,150 @@ log transactions return data -/obj/machinery/atm/Topic(href, list/href_list) - if(href_list["choice"]) - switch(href_list["choice"]) - if("transfer") - if(authenticated_account && linked_db) - var/transfer_amount = text2num(href_list["funds_amount"]) - if(transfer_amount <= 0) - alert("That is not a valid amount.") - else if(transfer_amount <= authenticated_account.money) - var/target_account_number = text2num(href_list["target_acc_number"]) - var/transfer_purpose = href_list["purpose"] - if(linked_db.charge_to_account(target_account_number, authenticated_account, transfer_purpose, machine_id, transfer_amount)) - to_chat(usr, "[bicon(src)]Funds transfer successful.") - else - to_chat(usr, "[bicon(src)]Funds transfer failed.") - - else - to_chat(usr, "[bicon(src)]You don't have enough funds to do that!") - if("view_screen") - view_screen = text2num(href_list["view_screen"]) - if("change_security_level") - if(authenticated_account) - var/new_sec_level = max( min(text2num(href_list["new_security_level"]), 2), 0) - authenticated_account.security_level = new_sec_level - if("attempt_auth") - if(linked_db) - if(!ticks_left_locked_down) - var/tried_account_num = text2num(href_list["account_num"]) - if(!tried_account_num && held_card) - tried_account_num = held_card.associated_account_number - var/tried_pin = text2num(href_list["account_pin"]) - - authenticated_account = attempt_account_access(tried_account_num, tried_pin, held_card && held_card.associated_account_number == tried_account_num ? 2 : 1) - if(!authenticated_account) - number_incorrect_tries++ - if(previous_account_number == tried_account_num) - if(number_incorrect_tries > max_pin_attempts) - //lock down the atm - ticks_left_locked_down = 30 - playsound(src, 'sound/machines/buzz-two.ogg', 50, 1) - - //create an entry in the account transaction log - var/datum/money_account/failed_account = linked_db.get_account(tried_account_num) - if(failed_account) - var/datum/transaction/T = new() - T.target_name = failed_account.owner_name - T.purpose = "Unauthorised login attempt" - T.source_terminal = machine_id - T.date = GLOB.current_date_string - T.time = station_time_timestamp() - failed_account.transaction_log.Add(T) - else - to_chat(usr, "[bicon(src)]Incorrect pin/account combination entered, [max_pin_attempts - number_incorrect_tries] attempts remaining.") - previous_account_number = tried_account_num - playsound(src, 'sound/machines/buzz-sigh.ogg', 50, 1) - else - to_chat(usr, "[bicon(src)]Incorrect pin/account combination entered.") - number_incorrect_tries = 0 - else - playsound(src, 'sound/machines/twobeep.ogg', 50, 1) - ticks_left_timeout = 120 - view_screen = NO_SCREEN - - //create a transaction log entry - var/datum/transaction/T = new() - T.target_name = authenticated_account.owner_name - T.purpose = "Remote terminal access" - T.source_terminal = machine_id - T.date = GLOB.current_date_string - T.time = station_time_timestamp() - authenticated_account.transaction_log.Add(T) - to_chat(usr, "[bicon(src)]Access granted. Welcome user '[authenticated_account.owner_name].'") - previous_account_number = tried_account_num - if("withdrawal") - var/amount = max(text2num(href_list["funds_amount"]),0) - if(amount <= 0) - alert("That is not a valid amount.") - else if(authenticated_account && amount > 0) - if(amount <= authenticated_account.money) - playsound(src, 'sound/machines/chime.ogg', 50, 1) - - //remove the money - if(amount > 100000) // prevent crashes - to_chat(usr, "The ATM's screen flashes, 'Maximum single withdrawl limit reached, defaulting to 100,000.'") - amount = 100000 - withdraw_arbitrary_sum(amount) - authenticated_account.charge(amount, null, "Credit withdrawal", machine_id, authenticated_account.owner_name) - else - to_chat(usr, "[bicon(src)]You don't have enough funds to do that!") - if("balance_statement") - if(authenticated_account) - if(world.timeofday < lastprint + PRINT_DELAY) - to_chat(usr, "The [name] flashes an error on its display.") - return - lastprint = world.timeofday - playsound(loc, 'sound/goonstation/machines/printer_thermal.ogg', 50, 1) - var/obj/item/paper/R = new(loc) - R.name = "Account balance: [authenticated_account.owner_name]" - R.info = {"NT Automated Teller Account Statement

- Account holder: [authenticated_account.owner_name]
- Account number: [authenticated_account.account_number]
- Balance: $[authenticated_account.money]
- Date and time: [station_time_timestamp()], [GLOB.current_date_string]

- Service terminal ID: [machine_id]
"} - - //stamp the paper - var/image/stampoverlay = image('icons/obj/bureaucracy.dmi') - stampoverlay.icon_state = "paper_stamp-cent" - if(!R.stamped) - R.stamped = new() - R.stamped += /obj/item/stamp - R.overlays += stampoverlay - R.stamps += "
This paper has been stamped by the Automatic Teller Machine." - - playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 50, 1) - if("insert_card") - if(held_card) - held_card.forceMove(loc) - authenticated_account = null - if(ishuman(usr) && !usr.get_active_hand()) - usr.put_in_hands(held_card) - held_card = null +/obj/machinery/atm/tgui_act(action, params) + switch(action) + if("transfer") + if(!authenticated_account || !linked_db) + return + var/transfer_amount = text2num(params["funds_amount"]) + if(transfer_amount <= 0) + to_chat(usr, "[bicon(src)]That is not a valid amount.") + else if(transfer_amount <= authenticated_account.money) + var/target_account_number = text2num(params["target_acc_number"]) + var/transfer_purpose = params["purpose"] + if(linked_db.charge_to_account(target_account_number, authenticated_account, transfer_purpose, machine_id, transfer_amount)) + to_chat(usr, "[bicon(src)]Funds transfer successful.") else - var/obj/item/I = usr.get_active_hand() - if(istype(I, /obj/item/card/id)) - usr.drop_item() - I.forceMove(src) - held_card = I - if("logout") - authenticated_account = null + to_chat(usr, "[bicon(src)]Funds transfer failed.") + else + to_chat(usr, "[bicon(src)]You don't have enough funds to do that!") - SSnanoui.update_uis(src) - return 1 + if("view_screen") + var/list/valid_screen = list(DEFAULT_SCREEN, CHANGE_SECURITY_LEVEL, TRANSFER_FUNDS, VIEW_TRANSACTION_LOGS) + var/screen_proper = text2num(params["view_screen"]) + if(screen_proper in valid_screen) + view_screen = screen_proper + else + message_admins("Warning: possible href exploit by [key_name(usr)] - Invalid screen number passed into an ATM") + log_debug("Warning: possible href exploit by [key_name(usr)] - Invalid screen number passed into an ATM") + + if("change_security_level") + if(authenticated_account) + var/new_sec_level = max(min(text2num(params["new_security_level"]), 2), 0) + authenticated_account.security_level = new_sec_level + + if("attempt_auth") + if(linked_db) + if(!ticks_left_locked_down) + var/tried_account_num = text2num(params["account_num"]) + if(!tried_account_num && held_card) + tried_account_num = held_card.associated_account_number + var/tried_pin = text2num(params["account_pin"]) + + authenticated_account = attempt_account_access(tried_account_num, tried_pin, held_card && held_card.associated_account_number == tried_account_num ? 2 : 1) + if(!authenticated_account) + number_incorrect_tries++ + if(previous_account_number == tried_account_num) + if(number_incorrect_tries > max_pin_attempts) + //lock down the atm + ticks_left_locked_down = 30 + playsound(src, 'sound/machines/buzz-two.ogg', 50, TRUE) + + //create an entry in the account transaction log + var/datum/money_account/failed_account = linked_db.get_account(tried_account_num) + if(failed_account) + var/datum/transaction/T = new() + T.target_name = failed_account.owner_name + T.purpose = "Unauthorised login attempt" + T.source_terminal = machine_id + T.date = GLOB.current_date_string + T.time = station_time_timestamp() + failed_account.transaction_log.Add(T) + else + to_chat(usr, "[bicon(src)]Incorrect pin/account combination entered, [max_pin_attempts - number_incorrect_tries] attempt\s remaining.") + previous_account_number = tried_account_num + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE) + else + to_chat(usr, "[bicon(src)]Incorrect pin/account combination entered.") + number_incorrect_tries = 0 + else + playsound(src, 'sound/machines/twobeep.ogg', 50, TRUE) + ticks_left_timeout = LOCKOUT_TIME + view_screen = DEFAULT_SCREEN + + //create a transaction log entry + var/datum/transaction/T = new() + T.target_name = authenticated_account.owner_name + T.purpose = "Remote terminal access" + T.source_terminal = machine_id + T.date = GLOB.current_date_string + T.time = station_time_timestamp() + authenticated_account.transaction_log.Add(T) + to_chat(usr, "[bicon(src)]Access granted. Welcome user '[authenticated_account.owner_name].'") + previous_account_number = tried_account_num + + if("withdrawal") + var/amount = max(text2num(params["funds_amount"]),0) + if(amount <= 0) + to_chat(usr, "[bicon(src)]That is not a valid amount.") + else if(authenticated_account && amount > 0) + if(amount <= authenticated_account.money) + playsound(src, 'sound/machines/chime.ogg', 50, TRUE) + + //remove the money + if(amount > 100000) // prevent crashes + to_chat(usr, "The ATM's screen flashes, 'Maximum single withdrawl limit reached, defaulting to 100,000.'") + amount = 100000 + withdraw_arbitrary_sum(amount) + authenticated_account.charge(amount, null, "Credit withdrawal", machine_id, authenticated_account.owner_name) + else + to_chat(usr, "[bicon(src)]You don't have enough funds to do that!") + + if("balance_statement") + if(authenticated_account) + if(world.timeofday < lastprint + PRINT_DELAY) + to_chat(usr, "The [name] flashes an error on its display.") + return + lastprint = world.timeofday + playsound(loc, 'sound/goonstation/machines/printer_thermal.ogg', 50, TRUE) + var/obj/item/paper/R = new(loc) + R.name = "Account balance: [authenticated_account.owner_name]" + R.info = {"NT Automated Teller Account Statement

+ Account holder: [authenticated_account.owner_name]
+ Account number: [authenticated_account.account_number]
+ Balance: $[authenticated_account.money]
+ Date and time: [station_time_timestamp()], [GLOB.current_date_string]

+ Service terminal ID: [machine_id]
"} + + //stamp the paper + var/image/stampoverlay = image('icons/obj/bureaucracy.dmi') + stampoverlay.icon_state = "paper_stamp-cent" + if(!R.stamped) + R.stamped = new() + R.stamped += /obj/item/stamp + R.overlays += stampoverlay + R.stamps += "
This paper has been stamped by the Automatic Teller Machine." + + playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 50, TRUE) + + if("insert_card") + if(held_card) + held_card.forceMove(loc) + authenticated_account = null + if(ishuman(usr) && !usr.get_active_hand()) + usr.put_in_hands(held_card) + held_card = null + else + var/obj/item/I = usr.get_active_hand() + if(istype(I, /obj/item/card/id)) + usr.drop_item() + I.forceMove(src) + held_card = I + + if("logout") + authenticated_account = null + + . = TRUE //create the most effective combination of notes to make up the requested amount /obj/machinery/atm/proc/withdraw_arbitrary_sum(arbitrary_sum) diff --git a/code/modules/economy/Accounts.dm b/code/modules/economy/Accounts.dm index 19e2bcafe0b..67a6f117465 100644 --- a/code/modules/economy/Accounts.dm +++ b/code/modules/economy/Accounts.dm @@ -19,7 +19,7 @@ GLOBAL_LIST_EMPTY(all_money_accounts) GLOB.station_account = new() GLOB.station_account.owner_name = "[station_name()] Station Account" GLOB.station_account.account_number = rand(111111, 999999) - GLOB.station_account.remote_access_pin = rand(1111, 111111) + GLOB.station_account.remote_access_pin = rand(111111, 999999) GLOB.station_account.money = STATION_START_CASH //create an entry in the account transaction log for when it was created @@ -35,7 +35,7 @@ GLOBAL_LIST_EMPTY(all_money_accounts) var/datum/money_account/department_account = new() department_account.owner_name = "[department] Account" department_account.account_number = rand(111111, 999999) - department_account.remote_access_pin = rand(1111, 111111) + department_account.remote_access_pin = rand(111111, 999999) department_account.money = DEPARTMENT_START_CASH //create an entry in the account transaction log for when it was created @@ -55,7 +55,7 @@ GLOBAL_LIST_EMPTY(all_money_accounts) //create a new account var/datum/money_account/M = new() M.owner_name = new_owner_name - M.remote_access_pin = rand(1111, 111111) + M.remote_access_pin = rand(111111, 999999) M.money = starting_funds //create an entry in the account transaction log for when it was created diff --git a/nano/templates/atm.tmpl b/nano/templates/atm.tmpl deleted file mode 100644 index fa6c818b081..00000000000 --- a/nano/templates/atm.tmpl +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - -

Nanotrasen Automatic Teller Machine

-For all your monetary needs! -
-This terminal is {{:data.machine_id}}. Report this code when contacting Nanotrasen IT Support -
- -
-
Card:
-
{{:helper.link(data.held_card_name, 'eject', {'choice' : 'insert_card'})}}
-
-
-{{if data.ticks_left_locked_down > 0}} - Maximum number of pin attempts exceeded! Access to this ATM has been temporarily disabled. -{{else data.authenticated_account}} - {{if data.view_screen == 1}} -

Select a new security level for this account:

-
-
Level:
-
{{:helper.link('Zero', 'unlock', {'choice' : 'change_security_level', 'new_security_level' : 0}, data.security_level == 0 ? 'selected' : null)}}
-
-
-
Description:
-
Either the account number or card is required to access this account. EFTPOS transactions will require a card and ask for a pin, but not verify the pin is correct.
-
-
-
-
Level:
-
{{:helper.link('One', 'unlock-alt', {'choice' : 'change_security_level', 'new_security_level' : 1}, data.security_level == 1 ? 'selected' : null)}}
-
-
-
Description:
-
An account number and pin must be manually entered to access this account and process transactions.
-
-
-
-
Level:
-
{{:helper.link('Two', 'lock', {'choice' : 'change_security_level', 'new_security_level' : 2}, data.security_level == 2 ? 'selected' : null)}}
-
-
-
Description:
-
In addition to account number and pin, a card is required to access this account and process transactions.
-
-
- {{:helper.link('Back', 'arrow-left', {'choice' : 'view_screen', 'view_screen' : 0})}} - {{else data.view_screen == 2}} -
Account balance: ${{:helper.smoothRound(data.money)}}
-
- - -
Target account number:
-
- -
Funds to transfer:
-
- -
Transaction purpose:
-
- -
-
- {{:helper.link('Back', 'arrow-left', {'choice' : 'view_screen', 'view_screen' : 0})}} - {{else data.view_screen == 3}} -

Transaction logs

-
{{:helper.link('Back', 'arrow-left', {'choice' : 'view_screen', 'view_screen' : 0})}}
- -
- {{if data.transaction_log}} - - - - - - - - - - - - {{for data.transaction_log}} - - - - - - - - {{/for}} - -
TimestampTargetReasonValueTerminal
{{:value.date}} {{:value.time}}{{:value.target_name}}{{:value.purpose}}${{:value.amount}}{{:value.source_terminal}}
- {{else}} - Your account has no financial transactions on record for today. - {{/if}} -
- {{else}} - Welcome, {{:data.owner_name}}.
- Account balance: ${{:helper.smoothRound(data.money)}} -
- - -
-
-
- -

Menu

-
{{:helper.link('Change account security level', 'lock', {'choice' : 'view_screen', 'view_screen' : 1})}}
-
{{:helper.link('Make transfer', 'exchange', {'choice' : 'view_screen', 'view_screen' : 2})}}
-
{{:helper.link('View transaction log', 'list', {'choice' : 'view_screen', 'view_screen' : 3})}}
-
{{:helper.link('Print balance statement', 'print', {'choice' : 'balance_statement'})}}
-
{{:helper.link('Logout', 'sign-out', {'choice' : 'logout'})}}
- {{/if}} -{{else data.linked_db}} -
- - -
Account:
-
- -
PIN:
-
-
- -
-{{else}} - Unable to connect to accounts database, please retry and if the issue persists contact Nanotrasen IT support. -{{/if}} \ No newline at end of file diff --git a/tgui/packages/tgui/interfaces/ATM.js b/tgui/packages/tgui/interfaces/ATM.js new file mode 100644 index 00000000000..585482e27bc --- /dev/null +++ b/tgui/packages/tgui/interfaces/ATM.js @@ -0,0 +1,380 @@ +import { Fragment } from 'inferno'; +import { useBackend, useLocalState } from '../backend'; +import { Button, Table, Icon, Input, Divider, Box, LabeledList, Section } from '../components'; +import { Window } from '../layouts'; + +/* +#define NO_SCREEN 0 +#define CHANGE_SECURITY_LEVEL 1 +#define TRANSFER_FUNDS 2 +#define VIEW_TRANSACTION_LOGS 3 +*/ + +export const ATM = (props, context) => { + const { act, data } = useBackend(context); + const { + view_screen, + authenticated_account, + ticks_left_locked_down, + linked_db, + } = data; + let body; + if (ticks_left_locked_down > 0) { + body = ( + + + Maximum number of pin attempts exceeded! Access to this ATM has been temporarily disabled. + + ); + } else if (!linked_db) { + body = ( + + + Unable to connect to accounts database, please retry and if the issue persists contact Nanotrasen IT support. + + ); + } else if (authenticated_account) { + switch (view_screen) { + case 1: // CHANGE_SECURITY_LEVEL + body = ; + break; + case 2: // TRANSFER_FUNDS + body = ; + break; + case 3: // VIEW_TRANSACTION_LOGS + body = ; + break; + default: + body = ; + } + } + else { + body = ; + } + return ( + + + +
+ {body} +
+
+
+ ); +}; + +const IntroductionAndCard = (props, context) => { + const { act, data } = useBackend(context); + const { + machine_id, + held_card_name, + } = data; + return ( +
+ + For all your monetary need! + + + + This terminal is {machine_id}, report this code when contacting Nanotrasen IT Support. + + + + +
+ ); +}; + +const ChangeSecurityLevel = (props, context) => { + const { act, data } = useBackend(context); + const { + security_level, + } = data; + return ( +
+ + + +
+ ); +}; + +const TransferFunds = (props, context) => { + const { act, data } = useBackend(context); + const [targetAccNumber, setTargetAccNumber] = useLocalState(context, "targetAccNumber", 0); + const [fundsAmount, setFundsAmount] = useLocalState(context, "fundsAmount", 0); + const [purpose, setPurpose] = useLocalState(context, "purpose", 0); + const { + money, + } = data; + return ( +
+ + + ${money} + + + setTargetAccNumber(value)} + /> + + + setFundsAmount(value)} + /> + + + setPurpose(value)} + /> + + + +
+ ); +}; + +const DefaultScreen = (props, context) => { + const { act, data } = useBackend(context); + const [fundsAmount, setFundsAmount] = useLocalState(context, "fundsAmount", 0); + const { + owner_name, + money, + } = data; + return ( + +
act('logout') + } /> + }> + + + ${money} + + + setFundsAmount(value)} /> + + +
+
+ +
+
+ ); +}; + +const LoginScreen = (props, context) => { + const { act, data } = useBackend(context); + const [accountID, setAccountID] = useLocalState(context, "accountID", null); + const [accountPin, setAccountPin] = useLocalState(context, "accountPin", null); + const { + machine_id, + held_card_name, + } = data; + return ( +
+ + + setAccountID(value)} + /> + + + setAccountPin(value)} + /> + + +
+ ); +}; + +const ViewTransactionLogs = (props, context) => { + const { act, data } = useBackend(context); + const { + transaction_log, + } = data; + return ( +
+ + + + Timestamp + + + Target + + + Reason + + + Value + + + Terminal + + + {transaction_log.map(t => ( + + + {t.date} {t.time} + + + {t.target_name} + + + {t.purpose} + + + ${t.amount} + + + {t.source_terminal} + + + ))} +
+ + +
+ ); +}; + +const BackButton = (props, context) => { + const { act, data } = useBackend(context); + return ( +