From cdfd5df7bea94ad4286c492fb7b885f6e7433e0f Mon Sep 17 00:00:00 2001
From: Jeremiah <42397676+jlsnow301@users.noreply.github.com>
Date: Mon, 31 Jan 2022 12:47:28 -0800
Subject: [PATCH] Refactors pay stands + custom vendors (#63889)
I am disgruntled by the way pay stations work. They're not intuitive, they're a pain to build and have no interface. Basically: They don't get made, and the potential is lost.
Pay stands => Holopay
Summoned by right clicking your ID
Disappears if the card is out of range.
New TGUI window that offers more customization
Other bundled fixes:
Custom vendors become more user friendly
Code improvement
Lots of documentation + refactoring
New bundled number input will likely take place of animated number in tgui input number
Why It's Good For The Game
More RP opportunity for players, plus bug fixes. It's now much easier for players to start their own in game business selling substances clown shoes.
Changelog
cl
code: Created a new input component that accepts only integers. More usage to come.
refactor: Pay stands are now holographic. It's 2562! Create one by right-clicking your ID.
del: Circuit boards for pay stands.
refactor: Pay stands now have their own TGUI.
fix: Custom vendors now alert you when someone makes a purchase.
fix: Custom vendors now place items in your hand when you make a purchase.
/cl
---
_maps/RandomRuins/SpaceRuins/hellfactory.dmm | 6 +-
.../map_files/Deltastation/DeltaStation2.dmm | 3 -
.../map_files/IceBoxStation/IceBoxStation.dmm | 1 -
_maps/map_files/KiloStation/KiloStation.dmm | 2 -
_maps/map_files/MetaStation/MetaStation.dmm | 1 -
_maps/map_files/tramstation/tramstation.dmm | 1 -
code/game/objects/items/cards_ids.dm | 128 ++++++++-
.../circuitboards/machine_circuitboards.dm | 6 -
code/modules/art/paintings.dm | 2 +-
code/modules/economy/holopay.dm | 268 ++++++++++++++++++
code/modules/economy/pay_stand.dm | 152 ----------
.../research/designs/machine_designs.dm | 8 -
code/modules/research/techweb/all_nodes.dm | 1 -
code/modules/vending/_vending.dm | 125 ++++----
tgstation.dme | 2 +-
.../tgui/components/RestrictedInput.js | 154 ++++++++++
tgui/packages/tgui/components/index.js | 1 +
tgui/packages/tgui/interfaces/HoloPay.tsx | 237 ++++++++++++++++
18 files changed, 853 insertions(+), 245 deletions(-)
create mode 100644 code/modules/economy/holopay.dm
delete mode 100644 code/modules/economy/pay_stand.dm
create mode 100644 tgui/packages/tgui/components/RestrictedInput.js
create mode 100644 tgui/packages/tgui/interfaces/HoloPay.tsx
diff --git a/_maps/RandomRuins/SpaceRuins/hellfactory.dmm b/_maps/RandomRuins/SpaceRuins/hellfactory.dmm
index ec709f93ef0..df0146b7db5 100644
--- a/_maps/RandomRuins/SpaceRuins/hellfactory.dmm
+++ b/_maps/RandomRuins/SpaceRuins/hellfactory.dmm
@@ -72,10 +72,6 @@
/obj/item/storage/cans/sixbeer,
/turf/open/floor/iron/grimy,
/area/ruin/space/has_grav/hellfactoryoffice)
-"am" = (
-/obj/machinery/paystand,
-/turf/open/floor/iron/grimy,
-/area/ruin/space/has_grav/hellfactoryoffice)
"an" = (
/obj/effect/decal/remains/human,
/obj/effect/decal/cleanable/blood/old,
@@ -1367,7 +1363,7 @@ aa
aa
aa
ah
-am
+ao
ao
aH
ao
diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm
index 3c1144cd81f..9e2e15945dc 100644
--- a/_maps/map_files/Deltastation/DeltaStation2.dmm
+++ b/_maps/map_files/Deltastation/DeltaStation2.dmm
@@ -55557,7 +55557,6 @@
"kbN" = (
/obj/structure/table,
/obj/item/stack/sheet/iron/five,
-/obj/item/circuitboard/machine/paystand,
/obj/effect/turf_decal/tile/brown{
dir = 4
},
@@ -82182,7 +82181,6 @@
/area/cargo/office)
"qPc" = (
/obj/structure/table/reinforced,
-/obj/item/circuitboard/machine/paystand,
/obj/item/stack/cable_coil/five,
/obj/machinery/airalarm/directional/north,
/turf/open/floor/plating,
@@ -97226,7 +97224,6 @@
dir = 4
},
/obj/structure/table,
-/obj/item/circuitboard/machine/paystand,
/obj/item/wrench,
/obj/item/screwdriver,
/obj/effect/turf_decal/tile/neutral{
diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm
index e349126b7ad..b458bfc8049 100644
--- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm
+++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm
@@ -21575,7 +21575,6 @@
/obj/structure/table,
/obj/machinery/firealarm/directional/north,
/obj/item/stack/sheet/iron/five,
-/obj/item/circuitboard/machine/paystand,
/obj/item/stack/cable_coil/five,
/obj/effect/turf_decal/tile/brown{
dir = 4
diff --git a/_maps/map_files/KiloStation/KiloStation.dmm b/_maps/map_files/KiloStation/KiloStation.dmm
index ea645f26a51..ad7a924fd83 100644
--- a/_maps/map_files/KiloStation/KiloStation.dmm
+++ b/_maps/map_files/KiloStation/KiloStation.dmm
@@ -15451,7 +15451,6 @@
},
/obj/effect/decal/cleanable/dirt,
/obj/item/folder/red,
-/obj/item/circuitboard/machine/paystand,
/obj/structure/spider/stickyweb,
/turf/open/floor/iron/dark,
/area/maintenance/port)
@@ -61919,7 +61918,6 @@
/obj/machinery/firealarm/directional/west,
/obj/machinery/newscaster/directional/south,
/obj/item/clipboard,
-/obj/item/circuitboard/machine/paystand,
/turf/open/floor/iron/dark,
/area/commons/vacant_room/commissary)
"qeZ" = (
diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm
index abcc33c180d..ec538ac1c89 100644
--- a/_maps/map_files/MetaStation/MetaStation.dmm
+++ b/_maps/map_files/MetaStation/MetaStation.dmm
@@ -38480,7 +38480,6 @@
/obj/item/radio/intercom/directional/east{
pixel_y = 8
},
-/obj/item/circuitboard/machine/paystand,
/obj/item/stack/cable_coil/five,
/obj/effect/turf_decal/tile/brown{
dir = 4
diff --git a/_maps/map_files/tramstation/tramstation.dmm b/_maps/map_files/tramstation/tramstation.dmm
index b188a42948c..d366b740acd 100644
--- a/_maps/map_files/tramstation/tramstation.dmm
+++ b/_maps/map_files/tramstation/tramstation.dmm
@@ -61970,7 +61970,6 @@
/obj/effect/turf_decal/trimline/neutral/filled/line{
dir = 4
},
-/obj/item/circuitboard/machine/paystand,
/obj/item/stack/sheet/iron/five,
/obj/item/stack/cable_coil/five,
/turf/open/floor/iron,
diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm
index 93d39e6204b..68ecbf133f3 100644
--- a/code/game/objects/items/cards_ids.dm
+++ b/code/game/objects/items/cards_ids.dm
@@ -7,6 +7,9 @@
/// Fallback time if none of the config entries are set for USE_LOW_LIVING_HOUR_INTERN
#define INTERN_THRESHOLD_FALLBACK_HOURS 15
+/// Max time interval between projecting holopays
+#define HOLOPAY_PROJECTION_INTERVAL 7 SECONDS
+
/* Cards
* Contains:
* DATA CARD
@@ -59,8 +62,28 @@
var/registered_name = null
/// Linked bank account.
var/datum/bank_account/registered_account
- /// Linked paystand.
- var/obj/machinery/paystand/my_store
+
+ /// Linked holopay.
+ var/datum/weakref/holopay_ref
+ /// Cooldown between projecting holopays
+ COOLDOWN_DECLARE(last_holopay_projection)
+ /// List of logos available for holopay customization - via font awesome 5
+ var/static/list/available_logos = list("angry", "ankh", "bacon", "band-aid", "cannabis", "cat", "cocktail", "coins", "comments-dollar",
+ "cross", "cut", "dog", "donate", "dna", "fist-raised", "flask", "glass-cheers", "glass-martini-alt", "hamburger", "hand-holding-usd",
+ "hat-wizard", "head-side-cough-slash", "heart", "heart-broken", "laugh-beam", "leaf", "money-check-alt", "music", "piggy-bank",
+ "pizza-slice", "prescription-bottle-alt", "radiation", "robot", "smile", "skull-crossbones", "smoking", "space-shuttle", "tram",
+ "trash", "user-ninja", "utensils", "wrench")
+ /// Replaces the "pay whatever" functionality with a set amount when non-zero.
+ var/holopay_fee = 0
+ /// The holopay icon chosen by the user
+ var/holopay_logo = "donate"
+ /// Maximum forced fee. It's unlikely for a user to encounter this type of money, much less pay it willingly.
+ var/holopay_max_fee = 5000
+ /// Minimum forced fee for holopay stations. Registers as "pay what you want."
+ var/holopay_min_fee = 0
+ /// The holopay name chosen by the user
+ var/holopay_name = "holographic pay stand"
+
/// Registered owner's age.
var/registered_age = 30
@@ -94,8 +117,8 @@
/obj/item/card/id/Destroy()
if (registered_account)
registered_account.bank_cards -= src
- if (my_store && my_store.my_card == src)
- my_store.my_card = null
+ if (holopay_ref)
+ QDEL_NULL(holopay_ref)
return ..()
/obj/item/card/id/get_id_examine_strings(mob/user)
@@ -383,6 +406,103 @@
user.visible_message(span_notice("[user] shows you: [icon2html(src, viewers(user))] [src.name][minor]."), span_notice("You show \the [src.name][minor]."))
add_fingerprint(user)
+/obj/item/card/id/attack_hand_secondary(mob/user, list/modifiers)
+ . = ..()
+ if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
+ return
+ /// Sanity checks
+ var/obj/structure/holopay/my_store = holopay_ref?.resolve()
+ if(my_store)
+ my_store.dissapate()
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+ if(!COOLDOWN_FINISHED(src, last_holopay_projection))
+ balloon_alert(user, "still recharging")
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+ if(!registered_account || !registered_account.account_job)
+ balloon_alert(user, "no account")
+ to_chat(user, span_warning("You need a valid bank account to do this."))
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+ /// Determines where the holopay will be placed based on tile contents
+ var/turf/projection
+ var/turf/step_ahead = get_step(user, user.dir)
+ var/turf/user_loc = user.loc
+ if(can_proj_holopay(step_ahead))
+ projection = step_ahead
+ else if(can_proj_holopay(user_loc))
+ projection = user_loc
+ if(!projection)
+ balloon_alert(user, "no space")
+ to_chat(user, span_warning("You need to be standing on or near an open tile to do this."))
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+ /// Success: Valid tile for holopay placement
+ var/obj/structure/holopay/new_store = new(projection)
+ if(new_store?.assign_card(projection, src))
+ COOLDOWN_START(src, last_holopay_projection, HOLOPAY_PROJECTION_INTERVAL)
+ playsound(projection, "sound/effects/empulse.ogg", 40, TRUE)
+ holopay_ref = WEAKREF(new_store)
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+/**
+ * Determines whether a new holopay can be placed on the given turf.
+ * Checks if there are dense contents, too many contents, or another
+ * holopay already exists on the turf.
+ *
+ * Arguments:
+ * * turf/target - The target turf to be checked for dense contents
+ * Returns:
+ * * TRUE if the target is a valid holopay location, FALSE otherwise.
+ */
+/obj/item/card/id/proc/can_proj_holopay(turf/target)
+ if(!isfloorturf(target))
+ return FALSE
+ if(target.density)
+ return FALSE
+ if(length(target.contents) > 5)
+ return FALSE
+ for(var/obj/checked_obj in target.contents)
+ if(checked_obj.density)
+ return FALSE
+ if(istype(checked_obj, /obj/structure/holopay))
+ return FALSE
+ return TRUE
+
+/**
+ * Setter for the shop logo on linked holopays
+ *
+ * Arguments:
+ * * new_logo - The new logo to be set.
+ */
+/obj/item/card/id/proc/set_holopay_logo(new_logo)
+ if(!available_logos.Find(new_logo))
+ CRASH("User input a holopay shop logo that didn't exist.")
+ holopay_logo = new_logo
+
+/**
+ * Setter for changing the force fee on a holopay.
+ *
+ * Arguments:
+ * * new_fee - The new fee to be set.
+ */
+/obj/item/card/id/proc/set_holopay_fee(new_fee)
+ if(!isnum(new_fee))
+ CRASH("User input a non number into the holopay fee field.")
+ if(new_fee < holopay_min_fee || new_fee > holopay_max_fee)
+ CRASH("User input a number outside of the valid range into the holopay fee field.")
+ holopay_fee = new_fee
+
+/**
+ * Setter for changing the holopay name.
+ *
+ * Arguments:
+ * * new_name - The new name to be set.
+ */
+/obj/item/card/id/proc/set_holopay_name(name)
+ if(length(name) < 3 || length(name) > MAX_NAME_LEN)
+ to_chat(usr, span_warning("Must be between 3 - 42 characters."))
+ else
+ holopay_name = html_encode(trim(name, MAX_NAME_LEN))
+
+
/obj/item/card/id/vv_edit_var(var_name, var_value)
. = ..()
if(.)
diff --git a/code/game/objects/items/circuitboards/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machine_circuitboards.dm
index a9e777793f1..cfaefdec9f5 100644
--- a/code/game/objects/items/circuitboards/machine_circuitboards.dm
+++ b/code/game/objects/items/circuitboards/machine_circuitboards.dm
@@ -397,12 +397,6 @@
/obj/item/stock_parts/manipulator = 1)
def_components = list(/obj/item/stack/ore/bluespace_crystal = /obj/item/stack/ore/bluespace_crystal/artificial)
-/obj/item/circuitboard/machine/paystand
- name = "Pay Stand (Machine Board)"
- greyscale_colors = CIRCUIT_COLOR_GENERIC
- build_path = /obj/machinery/paystand
- req_components = list()
-
/obj/item/circuitboard/machine/protolathe
name = "Protolathe (Machine Board)"
greyscale_colors = CIRCUIT_COLOR_GENERIC
diff --git a/code/modules/art/paintings.dm b/code/modules/art/paintings.dm
index b536ece86ef..a4f76b2a225 100644
--- a/code/modules/art/paintings.dm
+++ b/code/modules/art/paintings.dm
@@ -173,7 +173,7 @@
to_chat(user,span_notice("You don't even have a id and you want to be an art patron?"))
return
if(!id_card.registered_account || !id_card.registered_account.account_job)
- to_chat(user,span_notice("No valid non-departamental account found."))
+ to_chat(user,span_notice("No valid non-departmental account found."))
return
var/datum/bank_account/account = id_card.registered_account
if(account.account_balance < painting_metadata.credit_value)
diff --git a/code/modules/economy/holopay.dm b/code/modules/economy/holopay.dm
new file mode 100644
index 00000000000..fd1bd1e4e6b
--- /dev/null
+++ b/code/modules/economy/holopay.dm
@@ -0,0 +1,268 @@
+/obj/structure/holopay
+ name = "holographic pay stand"
+ desc = "an unregistered pay stand"
+ icon = 'icons/obj/economy.dmi'
+ icon_state = "card_scanner"
+ alpha = 150
+ anchored = TRUE
+ armor = list(MELEE = 0, BULLET = 50, LASER = 50, ENERGY = 50, BOMB = 0, BIO = 0, FIRE = 20, ACID = 20)
+ max_integrity = 15
+ layer = FLY_LAYER
+ /// ID linked to the holopay
+ var/datum/weakref/card_ref
+ /// Max range at which the hologram can be projected before it deletes
+ var/max_holo_range = 4
+ /// The holopay shop icon displayed in the UI
+ var/shop_logo = "donate"
+ /// Replaces the "pay whatever" functionality with a set amount when non-zero.
+ var/force_fee = 0
+
+/obj/structure/holopay/examine(mob/user)
+ . = ..()
+ if(force_fee)
+ . += span_boldnotice("This holopay forces a payment of [force_fee] credit\s per swipe instead of a variable amount.")
+
+/obj/structure/holopay/attack_hand(mob/living/user, list/modifiers)
+ . = ..()
+ if(.)
+ return
+ if(!user.combat_mode)
+ ui_interact(user)
+ return .
+ user.do_attack_animation(src, ATTACK_EFFECT_PUNCH)
+ user.changeNext_move(CLICK_CD_MELEE)
+ take_damage(5, BRUTE, MELEE, 1)
+
+/obj/structure/holopay/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0)
+ switch(damage_type)
+ if(BRUTE)
+ playsound(loc, 'sound/weapons/egloves.ogg', 80, TRUE)
+ if(BURN)
+ playsound(loc, 'sound/weapons/egloves.ogg', 80, TRUE)
+
+/obj/structure/holopay/deconstruct()
+ dissapate()
+ return ..()
+
+/obj/structure/holopay/attackby(obj/item/held_item, mob/holder, params)
+ var/mob/living/user = holder
+ if(!isliving(user))
+ return ..()
+ /// Users can pay with an ID to skip the UI
+ if(istype(held_item, /obj/item/card/id))
+ if(force_fee && tgui_alert(holder, "This holopay has a [force_fee] cr fee. Confirm?", "Holopay Fee", list("Pay", "Cancel")) != "Pay")
+ return TRUE
+ process_payment(user)
+ return TRUE
+ /// Users can also pay by holochip
+ if(istype(held_item, /obj/item/holochip))
+ /// Account checks
+ var/obj/item/holochip/chip = held_item
+ if(!chip.credits)
+ balloon_alert(user, "holochip is empty")
+ to_chat(user, span_warning("There doesn't seem to be any credits here."))
+ return FALSE
+ /// Charges force fee or uses pay what you want
+ var/cash_deposit = force_fee || tgui_input_number(user, "How much? (Max: [chip.credits])", "Patronage", max_value = chip.credits)
+ /// Exit sanity checks
+ if(!cash_deposit)
+ return TRUE
+ if(QDELETED(held_item) || QDELETED(user) || QDELETED(src) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
+ return FALSE
+ if(!chip.spend(cash_deposit, FALSE))
+ balloon_alert(user, "insufficient credits")
+ to_chat(user, span_warning("You don't have enough credits to pay with this chip."))
+ return FALSE
+ /// Success: Alert buyer
+ alert_buyer(user, cash_deposit)
+ return TRUE
+ /// Throws errors if they try to use space cash
+ if(istype(held_item, /obj/item/stack/spacecash))
+ to_chat(user, "What is this, the 2000s? We only take card here.")
+ return TRUE
+ if(istype(held_item, /obj/item/coin))
+ to_chat(user, "What is this, the 1800s? We only take card here.")
+ return TRUE
+ return ..()
+
+/obj/structure/holopay/ui_interact(mob/user, datum/tgui/ui)
+ . = ..()
+ if(.)
+ return FALSE
+ var/mob/living/interactor = user
+ if(isliving(interactor) && interactor.combat_mode)
+ return FALSE
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "HoloPay")
+ ui.open()
+
+/obj/structure/holopay/ui_status(mob/user)
+ . = ..()
+ if(!in_range(user, src) && !isobserver(user))
+ return UI_CLOSE
+
+/obj/structure/holopay/ui_static_data(mob/user)
+ . = list()
+ /// Sanity checks
+ var/obj/item/card/id/linked_card = get_card()
+ .["available_logos"] = linked_card.available_logos
+ .["description"] = desc
+ .["max_fee"] = linked_card.holopay_max_fee
+ .["owner"] = linked_card.registered_account?.account_holder || null
+ .["shop_logo"] = shop_logo
+
+/obj/structure/holopay/ui_data(mob/user)
+ . = list()
+ .["force_fee"] = force_fee
+ .["name"] = name
+ if(!isliving(user))
+ return .
+ var/mob/living/card_holder = user
+ var/obj/item/card/id/id_card = card_holder.get_idcard(TRUE)
+ var/datum/bank_account/account = id_card?.registered_account || null
+ if(account)
+ .["user"] = list()
+ .["user"]["name"] = account.account_holder
+ .["user"]["balance"] = account.account_balance
+
+/obj/structure/holopay/ui_act(action, list/params, datum/tgui/ui)
+ . = ..()
+ if(.)
+ return FALSE
+ var/obj/item/card/id/linked_card = get_card()
+ switch(action)
+ if("done")
+ ui.send_full_update()
+ return TRUE
+ if("fee")
+ linked_card.set_holopay_fee(params["amount"])
+ force_fee = linked_card.holopay_fee
+ if("logo")
+ linked_card.set_holopay_logo(params["logo"])
+ shop_logo = linked_card.holopay_logo
+ if("pay")
+ ui.close()
+ process_payment(usr)
+ return TRUE
+ if("rename")
+ linked_card.set_holopay_name(params["name"])
+ name = linked_card.holopay_name
+ return FALSE
+
+/**
+ * Links the source card to the holopay. Begins checking if its in range.
+ *
+ * Parameters:
+ * * turf/target - The tile to project the holopay onto
+ * * obj/item/card/id/card - The card to link to the holopay
+ * Returns:
+ * * TRUE - the card was linked
+ */
+/obj/structure/holopay/proc/assign_card(turf/target, obj/item/card/id/card)
+ card_ref = WEAKREF(card)
+ desc = "Pays directly into [card.registered_account.account_holder]'s bank account."
+ force_fee = card.holopay_fee
+ shop_logo = card.holopay_logo
+ name = card.holopay_name
+ add_atom_colour("#77abff", FIXED_COLOUR_PRIORITY)
+ set_light(2)
+ visible_message(span_notice("A holographic pay stand appears."))
+ /// Start checking if the source projection is in range
+ RegisterSignal(card, COMSIG_MOVABLE_MOVED, .proc/check_operation)
+ if(card.loc)
+ RegisterSignal(card.loc, COMSIG_MOVABLE_MOVED, .proc/check_operation)
+ return TRUE
+
+/**
+ * A periodic check to see if the projecting card is nearby.
+ * Deletes the holopay if true.
+ */
+/obj/structure/holopay/proc/check_operation()
+ SIGNAL_HANDLER
+ var/obj/item/card/id/linked_card = get_card()
+ if(!IN_GIVEN_RANGE(src, linked_card, max_holo_range) || !IN_GIVEN_RANGE(src, linked_card.loc, max_holo_range))
+ dissapate()
+
+/**
+ * Creates holopay vanishing effects.
+ * Deletes the holopay thereafter.
+ */
+/obj/structure/holopay/proc/dissapate()
+ var/obj/item/card/id/linked_card = get_card()
+ playsound(loc, "sound/effects/empulse.ogg", 40, TRUE)
+ visible_message(span_notice("The pay stand vanishes."))
+ QDEL_NULL(linked_card.holopay_ref)
+
+/**
+ * Checks that the card is still linked.
+ * Deletes the holopay if not.
+ *
+ * Returns:
+ * * /obj/item/card/id/card - The card that is linked to the holopay
+ */
+/obj/structure/holopay/proc/get_card()
+ var/obj/item/card/id/linked_card = card_ref?.resolve()
+ if(!linked_card || !istype(linked_card, /obj/item/card/id))
+ stack_trace("Could not link a holopay to a valid card.")
+ qdel(src)
+ return linked_card
+
+/**
+ * Initiates a transaction between accounts.
+ *
+ * Parameters:
+ * * mob/living/user - The user who initiated the transaction.
+ * Returns:
+ * * TRUE - transaction was successful
+ */
+/obj/structure/holopay/proc/process_payment(mob/living/user)
+ // Preliminary sanity checks
+ var/obj/item/card/id/linked_card = get_card()
+ /// Account checks
+ var/obj/item/card/id/id_card
+ id_card = user.get_idcard(TRUE)
+ if(!id_card || !id_card.registered_account || !id_card.registered_account.account_job)
+ balloon_alert(user, "invalid account")
+ to_chat(user, span_warning("You don't have a valid account."))
+ return FALSE
+ var/datum/bank_account/payee = id_card.registered_account
+ if(payee == linked_card.registered_account)
+ balloon_alert(user, "invalid transaction")
+ to_chat(user, span_warning("You can't pay yourself."))
+ return FALSE
+ /// If the user has enough money, ask them the amount or charge the force fee
+ var/amount = force_fee || tgui_input_number(user, "How much? (Max: [payee.account_balance])", "Patronage", max_value = payee.account_balance)
+ /// Exit checks in case the user cancelled or entered an invalid amount
+ if(!amount || QDELETED(user) || QDELETED(src) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
+ return FALSE
+ if(!payee.adjust_money(-amount))
+ balloon_alert(user, "insufficient credits")
+ to_chat(user, span_warning("You don't have the money to pay for this."))
+ return FALSE
+ /// Success: Alert the buyer
+ alert_buyer(user, amount)
+ return TRUE
+
+/**
+ * Alerts the owner of the transaction.
+ *
+ * Parameters:
+ * * payee - The user who initiated the transaction.
+ * * amount - The amount of money that was paid.
+ * Returns:
+ * * TRUE - alert was successful.
+ */
+/obj/structure/holopay/proc/alert_buyer(payee, amount)
+ /// Sanity checks
+ var/obj/item/card/id/linked_card = get_card()
+ /// Pay the owner
+ linked_card.registered_account.adjust_money(amount)
+ /// Make alerts
+ linked_card.registered_account.bank_card_talk("[payee] has deposited [amount] cr at your holographic pay stand.")
+ say("Thank you for your patronage, [payee]!")
+ playsound(src, 'sound/effects/cashregister.ogg', 20, TRUE)
+ /// Log the event
+ log_econ("[amount] credits were transferred from [payee]'s transaction to [linked_card.registered_account.account_holder]")
+ SSblackbox.record_feedback("amount", "credits_transferred", amount)
+ return TRUE
diff --git a/code/modules/economy/pay_stand.dm b/code/modules/economy/pay_stand.dm
deleted file mode 100644
index fc84bab90dc..00000000000
--- a/code/modules/economy/pay_stand.dm
+++ /dev/null
@@ -1,152 +0,0 @@
-/obj/machinery/paystand
- name = "unregistered pay stand"
- desc = "See title."
- icon = 'icons/obj/economy.dmi'
- icon_state = "card_scanner"
- density = TRUE
- anchored = TRUE
- var/locked = FALSE
- var/obj/item/card/id/my_card
- var/obj/item/assembly/signaler/signaler //attached signaler, let people attach signalers that get activated if the user's transaction limit is achieved.
- var/signaler_threshold = 0 //signaler threshold amount
- var/amount_deposited = 0 //keep track of the amount deposited over time so you can pay multiple times to reach the signaler threshold
- var/force_fee = 0 //replaces the "pay whatever" functionality with a set amount when non-zero.
-
-/obj/machinery/paystand/attackby(obj/item/W, mob/user, params)
- if(istype(W, /obj/item/card/id))
- if(W == my_card)
- var/list/items = list(
- "Rename" = image(icon = 'icons/obj/economy.dmi', icon_state = "name"),
- "Set the fee" = image(icon = 'icons/obj/economy.dmi', icon_state = "fee")
- )
- var/choice = show_radial_menu(user, src, items, null, require_near = TRUE, tooltips = TRUE)
- if(choice == "Rename")
- var/rename_msg = tgui_input_text(user, "Rename the Paystand", "Paystand Name", name)
- if(!rename_msg || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
- return
- name = rename_msg
- return
- else if(choice == "Set the fee")
- var/force_fee_input = tgui_input_number(user, "Set the fee", "Fee", max_value = 10000)
- if(isnull(force_fee_input) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
- return
- force_fee = round(force_fee_input)
- return
- locked = !locked
- to_chat(user, span_notice("You [src.locked ? "lock" : "unlock"] the paystand, protecting the bolts from [anchored ? "loosening" : "tightening"]."))
- return
- if(!my_card)
- var/obj/item/card/id/new_card = W
- if(!new_card.registered_account)
- return
- var/msg = tgui_input_text(user, "Name of pay stand", "Paystand Naming", "Paystand (owned by [new_card.registered_account.account_holder])")
- if(!msg)
- return
- name = msg
- desc = "Owned by [new_card.registered_account.account_holder], pays directly into [user.p_their()] account."
- my_card = new_card
- to_chat(user, "You link the stand to your account.")
- return
- var/obj/item/card/id/pay_card = W
- if(pay_card.registered_account)
- if(!pay_card.registered_account.account_job)//Departmental budget cards like cargo's fall under this
- to_chat(user, span_warning("ERROR: Personal use of department budgets is not authorized."))
- return
- var/credit_amount = 0
- if(!force_fee)
- credit_amount = tgui_input_number(user, "How much would you like to deposit?", "Money Deposit")
- if(isnull(credit_amount))
- return
- credit_amount = round(credit_amount)
- else
- credit_amount = force_fee
- if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
- return
- if(credit_amount < 1)
- to_chat(user, span_warning("ERROR: Invalid amount designated."))
- return
- if(pay_card.registered_account.adjust_money(-credit_amount))
- purchase(pay_card.registered_account.account_holder, credit_amount)
- say("Thank you for your patronage, [pay_card.registered_account.account_holder]!")
- playsound(src, 'sound/effects/cashregister.ogg', 20, TRUE)
- return
- else
- to_chat(user, span_warning("ERROR: Account has insufficient funds to make transaction."))
- return
- else
- to_chat(user, span_warning("ERROR: No bank account assigned to identification card."))
- return
- if(istype(W, /obj/item/holochip))
- var/obj/item/holochip/H = W
- var/cashmoney = round(tgui_input_number(user, "How much would you like to deposit?", "Money Deposit"))
- if(isnull(cashmoney))
- return
- cashmoney = round(cashmoney)
- if(H.spend(cashmoney, FALSE))
- purchase(user, cashmoney)
- to_chat(user, "Thanks for purchasing! The vendor has been informed.")
- return
- else
- to_chat(user, span_warning("ERROR: Insufficient funds to make transaction."))
- return
- if(istype(W, /obj/item/stack/spacecash))
- to_chat(user, "What is this, the 2000s? We only take card here.")
- return
- if(istype(W, /obj/item/coin))
- to_chat(user, "What is this, the 1800s? We only take card here.")
- return
- if(istype(W, /obj/item/assembly/signaler))
- var/obj/item/assembly/signaler/S = W
- if(S.secured)
- to_chat(user, span_warning("The signaler needs to be in attachable mode to add it to the paystand!"))
- return
- if(!my_card)
- to_chat(user, span_warning("ERROR: No identification card has been assigned to this paystand yet!"))
- return
- if(!isnull(signaler))
- to_chat(user, span_warning("A signaler is already attached to this unit!"))
- return
- var/cash_limit = tgui_input_number(user, "Enter the minimum amount of cash needed to deposit before the signaler is activated.", "Signaler Activation Threshold", 1, min_value = 1)
- if(isnull(cash_limit))
- return
- cash_limit = round(cash_limit)
- S.forceMove(src)
- signaler = S
- signaler_threshold = cash_limit
- to_chat(user, "You attach the signaler to the paystand.")
- desc += " A signaler appears to be attached to the scanner."
-
- if(default_deconstruction_screwdriver(user, "card_scanner", "card_scanner", W))
- return
-
- else if(default_pry_open(W))
- return
-
- else if(default_unfasten_wrench(user, W))
- return
-
- else if(default_deconstruction_crowbar(W))
- return
- else
- return ..()
-
-/obj/machinery/paystand/proc/purchase(buyer, price)
- my_card.registered_account.adjust_money(price)
- my_card.registered_account.bank_card_talk("Purchase made at your vendor by [buyer] for [price] credits.")
- amount_deposited = amount_deposited + price
- if(signaler && amount_deposited >= signaler_threshold)
- signaler.signal()
- amount_deposited = 0
-
-/obj/machinery/paystand/default_unfasten_wrench(mob/user, obj/item/I, time = 20)
- if(locked)
- to_chat(user, span_warning("The bolts on this paystand are currently covered!"))
- return FALSE
- . = ..()
-
-/obj/machinery/paystand/examine(mob/user)
- . = ..()
- if(force_fee)
- . += span_warning("This paystand forces a payment of [force_fee] credit\s per swipe instead of a variable amount.")
- if(user.get_active_held_item() == my_card)
- . += span_notice("Paystands can be edited through swiping your card.")
diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm
index 4d17f148dec..e1787215787 100644
--- a/code/modules/research/designs/machine_designs.dm
+++ b/code/modules/research/designs/machine_designs.dm
@@ -572,14 +572,6 @@
category = list ("Research Machinery")
departmental_flags = DEPARTMENTAL_FLAG_CARGO
-/datum/design/board/paystand
- name = "Machine Design (Pay Stand)"
- desc = "The circuit board for a paystand."
- id = "paystand"
- build_path = /obj/item/circuitboard/machine/paystand
- category = list ("Misc. Machinery")
-
-
/datum/design/board/fat_sucker
name = "Machine Design (Lipid Extractor)"
desc = "The circuit board for a lipid extractor."
diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm
index 771f0811a01..a29538a3bec 100644
--- a/code/modules/research/techweb/all_nodes.dm
+++ b/code/modules/research/techweb/all_nodes.dm
@@ -35,7 +35,6 @@
"micro_mani",
"oven_tray",
"packagewrap",
- "paystand",
"plasmaglass",
"plasmareinforcedglass",
"plasteel",
diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm
index b3c06a23d68..dbbd5df74d6 100644
--- a/code/modules/vending/_vending.dm
+++ b/code/modules/vending/_vending.dm
@@ -1128,7 +1128,7 @@ GLOBAL_LIST_EMPTY(vending_products)
light_mask = "custom-light-mask"
refill_canister = /obj/item/vending_refill/custom
/// where the money is sent
- var/datum/bank_account/private_a
+ var/datum/bank_account/linked_account
/// max number of items that the custom vendor can hold
var/max_loaded_items = 20
/// Base64 cache of custom icons.
@@ -1139,9 +1139,9 @@ GLOBAL_LIST_EMPTY(vending_products)
. = FALSE
if(!isliving(user))
return FALSE
- var/mob/living/L = user
- var/obj/item/card/id/C = L.get_idcard(FALSE)
- if(C?.registered_account && C.registered_account == private_a)
+ var/mob/living/living_user = user
+ var/obj/item/card/id/id_card = living_user.get_idcard(FALSE)
+ if(id_card?.registered_account && id_card.registered_account == linked_account)
return TRUE
/obj/machinery/vending/custom/canLoadItem(obj/item/I, mob/user)
@@ -1158,6 +1158,12 @@ GLOBAL_LIST_EMPTY(vending_products)
if(I.custom_price)
return TRUE
+/obj/machinery/vending/custom/ui_interact(mob/user)
+ if(!linked_account)
+ balloon_alert(user, "no registered owner")
+ return FALSE
+ return ..()
+
/obj/machinery/vending/custom/ui_data(mob/user)
. = ..()
.["access"] = compartmentLoadAccessCheck(user)
@@ -1191,68 +1197,18 @@ GLOBAL_LIST_EMPTY(vending_products)
return
switch(action)
if("dispense")
- . = TRUE
- if(!vend_ready)
- return
- var/N = params["item"]
- var/obj/item/S
- vend_ready = FALSE
- var/obj/item/card/id/C
if(isliving(usr))
- var/mob/living/L = usr
- C = L.get_idcard(TRUE)
- if(!C)
- say("No card found.")
- flick(icon_deny,src)
- vend_ready = TRUE
- return
- else if (!C.registered_account)
- say("No account found.")
- flick(icon_deny,src)
- vend_ready = TRUE
- return
- var/datum/bank_account/account = C.registered_account
- for(var/obj/O in contents)
- if(format_text(O.name) == N)
- S = O
- break
- if(S)
- if(compartmentLoadAccessCheck(usr))
- vending_machine_input[N] = max(vending_machine_input[N] - 1, 0)
- S.forceMove(drop_location())
- loaded_items--
- use_power(5)
- vend_ready = TRUE
- updateUsrDialog()
- return
- if(account.has_money(S.custom_price))
- account.adjust_money(-S.custom_price)
- var/datum/bank_account/owner = private_a
- if(owner)
- owner.adjust_money(S.custom_price)
- SSblackbox.record_feedback("amount", "vending_spent", S.custom_price)
- log_econ("[S.custom_price] credits were spent on [src] buying a [S] by [owner.account_holder], owned by [private_a.account_holder].")
- vending_machine_input[N] = max(vending_machine_input[N] - 1, 0)
- S.forceMove(drop_location())
- loaded_items--
- use_power(5)
- if(last_shopper != REF(usr) || purchase_message_cooldown < world.time)
- say("Thank you for buying local and purchasing [S]!")
- purchase_message_cooldown = world.time + 5 SECONDS
- last_shopper = REF(usr)
- vend_ready = TRUE
- updateUsrDialog()
- return
- else
- say("You do not possess the funds to purchase this.")
+ vend_act(usr, params["item"])
vend_ready = TRUE
+ updateUsrDialog()
+ return TRUE
/obj/machinery/vending/custom/attackby(obj/item/I, mob/user, params)
- if(!private_a && isliving(user))
+ if(!linked_account && isliving(user))
var/mob/living/L = user
var/obj/item/card/id/C = L.get_idcard(TRUE)
if(C?.registered_account)
- private_a = C.registered_account
+ linked_account = C.registered_account
say("\The [src] has been linked to [C].")
if(compartmentLoadAccessCheck(user))
@@ -1277,6 +1233,57 @@ GLOBAL_LIST_EMPTY(vending_products)
explosion(src, devastation_range = -1, light_impact_range = 3)
return ..()
+/**
+ * Vends an item to the user. Handles all the logic:
+ * Updating stock, account transactions, alerting users.
+ * @return -- TRUE if a valid condition was met, FALSE otherwise.
+ */
+/obj/machinery/vending/custom/proc/vend_act(mob/living/user, choice)
+ if(!vend_ready)
+ return
+ var/obj/item/dispensed_item
+ var/obj/item/card/id/id_card = user.get_idcard(TRUE)
+ vend_ready = FALSE
+ if(!id_card || !id_card.registered_account || !id_card.registered_account.account_job)
+ balloon_alert(usr, "no card found")
+ flick(icon_deny, src)
+ return TRUE
+ var/datum/bank_account/payee = id_card.registered_account
+ for(var/obj/stock in contents)
+ if(format_text(stock.name) == choice)
+ dispensed_item = stock
+ break
+ if(!dispensed_item)
+ return FALSE
+ /// Charges the user if its not the owner
+ if(!compartmentLoadAccessCheck(user))
+ if(!payee.has_money(dispensed_item.custom_price))
+ balloon_alert(user, "insufficient funds")
+ return TRUE
+ /// Make the transaction
+ payee.adjust_money(-dispensed_item.custom_price)
+ linked_account.adjust_money(dispensed_item.custom_price)
+ linked_account.bank_card_talk("[payee.account_holder] made a [dispensed_item.custom_price] \
+ cr purchase at your custom vendor.")
+ /// Log the transaction
+ SSblackbox.record_feedback("amount", "vending_spent", dispensed_item.custom_price)
+ log_econ("[dispensed_item.custom_price] credits were spent on [src] buying a \
+ [dispensed_item] by [payee.account_holder], owned by [linked_account.account_holder].")
+ /// Make an alert
+ if(last_shopper != REF(usr) || purchase_message_cooldown < world.time)
+ say("Thank you for your patronage [user]!")
+ purchase_message_cooldown = world.time + 5 SECONDS
+ last_shopper = REF(usr)
+ /// Remove the item
+ loaded_items--
+ use_power(5)
+ vending_machine_input[choice] = max(vending_machine_input[choice] - 1, 0)
+ if(user.CanReach(src) && user.put_in_hands(dispensed_item))
+ to_chat(user, span_notice("You take [dispensed_item.name] out of the slot."))
+ else
+ to_chat(user, span_warning("[capitalize(dispensed_item.name)] falls onto the floor!"))
+ return TRUE
+
/obj/machinery/vending/custom/unbreakable
name = "Indestructible Vendor"
resistance_flags = INDESTRUCTIBLE
diff --git a/tgstation.dme b/tgstation.dme
index 4a16e2e5cb6..1f45bf7de61 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -2505,7 +2505,7 @@
#include "code\modules\discord\toggle_notify.dm"
#include "code\modules\economy\_economy.dm"
#include "code\modules\economy\account.dm"
-#include "code\modules\economy\pay_stand.dm"
+#include "code\modules\economy\holopay.dm"
#include "code\modules\emoji\emoji_parse.dm"
#include "code\modules\error_handler\error_handler.dm"
#include "code\modules\error_handler\error_viewer.dm"
diff --git a/tgui/packages/tgui/components/RestrictedInput.js b/tgui/packages/tgui/components/RestrictedInput.js
new file mode 100644
index 00000000000..c4ee1ea1a0d
--- /dev/null
+++ b/tgui/packages/tgui/components/RestrictedInput.js
@@ -0,0 +1,154 @@
+import { classes } from 'common/react';
+import { clamp } from 'common/math';
+import { Component, createRef } from 'inferno';
+import { Box } from './Box';
+import { KEY_ESCAPE, KEY_ENTER } from 'common/keycodes';
+
+const DEFAULT_MIN = 0;
+const DEFAULT_MAX = 10000;
+
+/**
+ * Takes a string input and parses integers from it.
+ * If none: Minimum is set.
+ * Else: Clamps it to the given range.
+ */
+const getClampedNumber = (value, minValue, maxValue) => {
+ const minimum = minValue || DEFAULT_MIN;
+ const maximum = maxValue || maxValue === 0 ? maxValue : DEFAULT_MAX;
+ if (!value || !value.length) {
+ return String(minimum);
+ }
+ let parsedValue = parseInt(value.replace(/\D/g, ''), 10);
+ if (isNaN(parsedValue)) {
+ return String(minimum);
+ } else {
+ return String(clamp(parsedValue, minimum, maximum));
+ }
+};
+
+export class RestrictedInput extends Component {
+ constructor() {
+ super();
+ this.inputRef = createRef();
+ this.state = {
+ editing: false,
+ };
+ this.handleBlur = (e) => {
+ const { editing } = this.state;
+ if (editing) {
+ this.setEditing(false);
+ }
+ };
+ this.handleChange = (e) => {
+ const { maxValue, minValue, onChange } = this.props;
+ e.target.value = getClampedNumber(e.target.value, minValue, maxValue);
+ if (onChange) {
+ onChange(e, +e.target.value);
+ }
+ };
+ this.handleFocus = (e) => {
+ const { editing } = this.state;
+ if (!editing) {
+ this.setEditing(true);
+ }
+ };
+ this.handleInput = (e) => {
+ const { editing } = this.state;
+ const { onInput } = this.props;
+ if (!editing) {
+ this.setEditing(true);
+ }
+ if (onInput) {
+ onInput(e, +e.target.value);
+ }
+ };
+ this.handleKeyDown = (e) => {
+ const { maxValue, minValue, onChange, onEnter } = this.props;
+ if (e.keyCode === KEY_ENTER) {
+ const safeNum = getClampedNumber(e.target.value, minValue, maxValue);
+ this.setEditing(false);
+ if (onChange) {
+ onChange(e, safeNum);
+ }
+ if (onEnter) {
+ onEnter(e, safeNum);
+ }
+ e.target.blur();
+ return;
+ }
+ if (e.keyCode === KEY_ESCAPE) {
+ if (this.props.onEscape) {
+ this.props.onEscape(e);
+ return;
+ }
+ this.setEditing(false);
+ e.target.value = this.props.value;
+ e.target.blur();
+ return;
+ }
+ };
+ }
+
+ componentDidMount() {
+ const { maxValue, minValue } = this.props;
+ const nextValue = this.props.value?.toString();
+ const input = this.inputRef.current;
+ if (input) {
+ input.value = getClampedNumber(nextValue, minValue, maxValue);
+ }
+ if (this.props.autoFocus || this.props.autoSelect) {
+ setTimeout(() => {
+ input.focus();
+
+ if (this.props.autoSelect) {
+ input.select();
+ }
+ }, 1);
+ }
+ }
+
+ componentDidUpdate(prevProps, _) {
+ const { maxValue, minValue } = this.props;
+ const { editing } = this.state;
+ const prevValue = prevProps.value?.toString();
+ const nextValue = this.props.value?.toString();
+ const input = this.inputRef.current;
+ if (input && !editing) {
+ if (nextValue !== prevValue && nextValue !== input.value) {
+ input.value = getClampedNumber(nextValue, minValue, maxValue);
+ }
+ }
+ }
+
+ setEditing(editing) {
+ this.setState({ editing });
+ }
+
+ render() {
+ const { props } = this;
+ const { onChange, onEnter, onInput, value, ...boxProps } = props;
+ const { className, fluid, monospace, ...rest } = boxProps;
+ return (
+
+ .
+
+
+ );
+ }
+}
diff --git a/tgui/packages/tgui/components/index.js b/tgui/packages/tgui/components/index.js
index 48cd147df39..121d2bfa4e2 100644
--- a/tgui/packages/tgui/components/index.js
+++ b/tgui/packages/tgui/components/index.js
@@ -33,6 +33,7 @@ export { NoticeBox } from './NoticeBox';
export { NumberInput } from './NumberInput';
export { ProgressBar } from './ProgressBar';
export { Popper } from './Popper';
+export { RestrictedInput } from './RestrictedInput';
export { RoundGauge } from './RoundGauge';
export { Section } from './Section';
export { Slider } from './Slider';
diff --git a/tgui/packages/tgui/interfaces/HoloPay.tsx b/tgui/packages/tgui/interfaces/HoloPay.tsx
new file mode 100644
index 00000000000..13fe55b4fec
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/HoloPay.tsx
@@ -0,0 +1,237 @@
+import { useBackend, useLocalState } from '../backend';
+import { Box, Button, Dropdown, Icon, NoticeBox, RestrictedInput, Section, Stack, Table, TextArea, Tooltip } from '../components';
+import { Window } from '../layouts';
+
+type HoloPayData = {
+ available_logos: string[];
+ balance: number;
+ description: string;
+ force_fee: number;
+ max_fee: number;
+ name: string;
+ owner: string;
+ shop_logo: string;
+ user: { name: string; balance: number };
+};
+
+const COPYRIGHT_SCROLLER = `Nanotrasen (c) 2525-2562. All sales final.
+Use of departmental funds is prohibited. For more information, visit
+the Head of Personnel. All rights reserved. All trademarks are property
+of their respective owners.`;
+
+export const HoloPay = (_, context) => {
+ const { data } = useBackend(context);
+ const { owner } = data;
+ const [setupMode, setSetupMode] = useLocalState(context, 'setupMode', false);
+ // User clicked the "Setup" or "Done" button.
+ const onClick = () => {
+ setSetupMode(!setupMode);
+ };
+
+ return (
+
+
+ {!owner ? (
+ Error! Swipe an ID first.
+ ) : (
+
+
+
+
+
+ {!setupMode ? (
+
+ ) : (
+
+ )}
+
+
+ )}
+
+
+ );
+};
+
+/**
+ * Displays the current user's bank information (if any)
+ */
+const AccountDisplay = (_, context) => {
+ const { data } = useBackend(context);
+ const { user } = data;
+ if (!user) {
+ return Error! No account detected.;
+ }
+
+ return (
+
+
+
+
+
+
+ {user?.name}
+
+
+
+
+ {user?.balance} cr
+
+
+
+
+
+ );
+};
+
+/**
+ * Displays the payment processor. This is the main display.
+ * Shows icon, name, payment button.
+ */
+const TerminalDisplay = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { description, force_fee, name, owner, user, shop_logo } = data;
+ const { onClick } = props;
+ const is_owner = owner === user?.name;
+ const cannot_pay
+ = is_owner || !user || user?.balance < 1 || user?.balance < force_fee;
+ const decodedName = name.replace(/(\d+);/g, (_, dec) => {
+ return String.fromCharCode(dec);
+ });
+
+ return (
+
+ Setup
+ )
+ }
+ fill
+ title="Terminal">
+
+
+
+
+
+
+
+ {decodedName}
+
+
+
+
+ {force_fee ? (
+
+
+ Pay {force_fee + ' cr'}
+ >
+ }
+ disabled={cannot_pay}
+ fluid
+ height="2rem"
+ onClick={() => act('pay')}
+ pt={0.2}
+ textAlign="center"
+ />
+ ) : (
+
+
+ {/* @ts-ignore */}
+
+
+
+
+ );
+};
+
+/**
+ * User has clicked "setup" button. Changes vars on the holopay.
+ */
+const SetupDisplay = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { available_logos = [], force_fee, max_fee, name, shop_logo } = data;
+ const { onClick } = props;
+ const decodedName = name.replace(/(\d+);/g, (_, dec) => {
+ return String.fromCharCode(dec);
+ });
+
+ return (
+ {
+ act('done');
+ onClick();
+ }}>
+ Done
+
+ }
+ fill
+ scrollable
+ title="Settings">
+
+
+
+ Shop Logo
+
+ act('logo', { logo: value })}
+ options={available_logos}
+ selected={shop_logo}
+ width="100%"
+ />
+
+
+
+ Name (3 - 42 chars)
+
+
+
+
+
+ Forced Fee
+
+ act('fee', { amount: value })}
+ value={force_fee}
+ />
+
+
+
+
+ );
+};