Skip to content
This repository was archived by the owner on Feb 7, 2026. It is now read-only.

Commit 525f8df

Browse files
authored
Merge pull request #393 from PyBotDevs/isocard-system-phase-3
Release Isocard System Phase 3
2 parents 760b803 + 4cb8f72 commit 525f8df

5 files changed

Lines changed: 200 additions & 6 deletions

File tree

api/runtimeconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"guild_log_whitelist": {},
1212
"ping_server_override": false,
1313
"debug_mode": false,
14-
"show_ping_on_startup": true
14+
"show_ping_on_startup": true,
15+
"isocard_server_enabled": true
1516
},
1617
"replit": false,
1718
"other_keys": {

cogs/isocard.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# Imports
44
import discord
55
import random
6+
import json
67
from framework.isobot.db.isocard import IsoCard
78
from discord import option, ApplicationContext, SlashCommandGroup
89
from discord.ext import commands
@@ -74,15 +75,16 @@ async def info(self, ctx: ApplicationContext, card_number: int):
7475
description="View a list of all your cards."
7576
)
7677
async def my_card(self, ctx: ApplicationContext):
77-
all_card_numbers = isocard_db.keys()
78+
all_card_numbers = isocard_db.fetch_all_cards()
79+
isocard_database = isocard_db.raw()
7880
your_cards = list()
7981
for card in all_card_numbers:
80-
if isocard_db[str(card)]["cardholder_user_id"] == ctx.author.id: your_cards.append(str(card))
82+
if isocard_database[str(card)]["cardholder_user_id"] == ctx.author.id: your_cards.append(str(card))
8183
embed_desc = str()
8284
sr = 1
8385
for card in your_cards:
84-
if isocard_db[str(card)]["config"]["card_label"] != None:
85-
embed_desc += f"{sr}. **{card}**: {isocard_db[str(card)]['config']['card_label']}\n"
86+
if isocard_database[str(card)]["config"]["card_label"] != None:
87+
embed_desc += f"{sr}. **{card}**: {isocard_database[str(card)]['config']['card_label']}\n"
8688
else: embed_desc += f"{sr}. **{card}**\n"
8789
sr += 1
8890
embed_desc += "\n*Nothing more here*"
@@ -108,5 +110,24 @@ async def options_label(self, ctx: ApplicationContext, card_number: int, new_lab
108110
if new_label == None: return await ctx.respond(embed=discord.Embed(description=":white_check_mark: Your card label has been reset.", color=discord.Color.green()), ephemeral=True)
109111
else: return await ctx.respond(embed=discord.Embed(description=":white_check_mark: Your card label has been edited.", color=discord.Color.green()), ephemeral=True)
110112

113+
@isocard.command(
114+
name="verify_transaction",
115+
description="Verify an ongoing transaction."
116+
)
117+
@option(name="verification_code", description="The 6-digit verification code for your transaction", type=int)
118+
async def verify_transaction(self, ctx: ApplicationContext, verification_code: int):
119+
try:
120+
with open("database/isocard_transactions.json", 'r') as f: transactions_db = json.load(f)
121+
if transactions_db[str(verification_code)]["payer_id"] == ctx.author.id:
122+
transactions_db[str(verification_code)]["status"] = "complete"
123+
with open("database/isocard_transactions.json", 'w+') as f: json.dump(transactions_db, f, indent=4)
124+
localembed = discord.Embed(
125+
title="Transaction successfully verified.",
126+
description="Please wait patiently until the merchant has verified the transaction.",
127+
color=discord.Color.green()
128+
)
129+
await ctx.respond(embed=localembed, ephemeral=True)
130+
except KeyError: return await ctx.respond("This transaction verification code is invalid.")
131+
111132
# Initialization
112133
def setup(bot): bot.add_cog(IsoCard(bot))

framework/isobot/db/isocard.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ def save(self, data: dict) -> int:
2020
with open("database/isocard.json", 'w+', encoding="utf-8") as f: json.dump(data, f, indent=4)
2121
return 0
2222

23+
def raw(self) -> dict:
24+
"""Returns all of the raw data from the IsoCard database.\n\n***WARNING:*** This function must **ONLY** be used for validation of IsoCard ownership, and nothing else."""
25+
isocard_db = self.load()
26+
return isocard_db
27+
28+
def fetch_all_cards(self) -> list:
29+
"""Fetches a `list` of all registered IsoCard numbers in the IsoCard database.\n\n***WARNING:*** This function must **ONLY** be used for validation of IsoCard ownership, and nothing else."""
30+
isocard_db = self.load()
31+
return list(isocard_db.keys())
32+
2333
def fetch_card_data(self, card_id: int) -> dict:
2434
"""Fetches the raw `dict` data related to the given IsoCard id.\n\nReturns data as `dict` if successful, returns `KeyError` if card id does not exist."""
2535
isocard_db = self.load()

framework/isobot/isocard.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
"""The IsoCard payments web server."""
2+
# Imports
3+
import json
4+
import random
5+
import logging
6+
from api import auth
7+
from flask import Flask
8+
from flask import request
9+
from framework.isobot import currency
10+
from threading import Thread
11+
12+
# Configuration
13+
log = logging.getLogger('werkzeug')
14+
log.setLevel(logging.ERROR)
15+
app = Flask('')
16+
currency = currency.CurrencyAPI("database/currency.json", "logs/currency.log")
17+
18+
def call_isocards_database() -> dict:
19+
"""Calls all of the latest information from the IsoCards database."""
20+
with open("database/isocard.json", 'r') as f: isocards = json.load(f)
21+
return isocards
22+
23+
def save(data):
24+
"""Dumps all cached databases to the local machine."""
25+
with open("database/isocard_transactions.json", 'w+') as f: json.dump(data, f, indent=4)
26+
27+
# Functions
28+
def generate_verification_code() -> int:
29+
"""Generates a random 6 digit verification code."""
30+
int_1 = str(random.randint(1, 9))
31+
int_2 = str(random.randint(0, 9))
32+
int_3 = str(random.randint(0, 9))
33+
int_4 = str(random.randint(0, 9))
34+
int_5 = str(random.randint(0, 9))
35+
int_6 = str(random.randint(0, 9))
36+
code: str = int_1 + int_2 + int_3 + int_4 + int_5 + int_6
37+
return int(code)
38+
39+
# API Commands
40+
@app.route('/', methods=["GET"])
41+
def main():
42+
return "Server is online."
43+
44+
@app.route('/requestpayment', methods=["GET"])
45+
def requestpayment():
46+
try:
47+
isocards = call_isocards_database()
48+
with open("database/isocard_transactions.json", 'r') as f: transactions_db = json.load(f)
49+
args = request.args
50+
card_number = args.get("cardnumber")
51+
ssc = args.get("ssc")
52+
amount = args.get("amount")
53+
merchant_id = args.get("merchantid")
54+
if str(isocards[str(card_number)]["ssc"]) == ssc:
55+
verification_code = generate_verification_code()
56+
user_id = isocards[str(card_number)]["cardholder_user_id"]
57+
transactions_db[str(verification_code)] = {
58+
"payer_id": user_id,
59+
"merchant_id": merchant_id,
60+
"card_number": card_number,
61+
"user_id": user_id,
62+
"amount": int(amount),
63+
"status": "in_progress"
64+
}
65+
save(transactions_db)
66+
request_data = {
67+
"code": 200,
68+
"message": f"Payment requested to IsoCard number: {card_number}. Payment will be complete once user accepts this.",
69+
"verification_code": verification_code
70+
}
71+
return request_data, 200
72+
else: return {
73+
"code": 401,
74+
"message": "Unable to authorize transaction."
75+
}, 401
76+
except Exception as e: return {
77+
"code": 500,
78+
"message": f"Failed to process payment: {e}",
79+
"exception": type(e).__name__
80+
}, 500
81+
82+
@app.route('/checkpayment', methods=["GET"])
83+
def checkpayment():
84+
try:
85+
with open("database/isocard_transactions.json", 'r') as f: transactions_db = json.load(f)
86+
args = request.args
87+
verification_code = args.get("verificationcode")
88+
if transactions_db[str(verification_code)]["status"] == "complete":
89+
if currency.get_bank(transactions_db[str(verification_code)]["payer_id"]) < transactions_db[str(verification_code)]["amount"]:
90+
del transactions_db[str(verification_code)]
91+
return {
92+
"code": 403,
93+
"message": "Transaction terminated: Insufficient payer balance.",
94+
"exception": "InsufficientFunds"
95+
}, 403
96+
currency.bank_remove(transactions_db[str(verification_code)]["payer_id"], transactions_db[str(verification_code)]["amount"])
97+
currency.bank_add(transactions_db[str(verification_code)]["merchant_id"], transactions_db[str(verification_code)]["amount"])
98+
del transactions_db[str(verification_code)]
99+
save(transactions_db)
100+
return {
101+
"code": 200,
102+
"message": "Transaction complete."
103+
}, 200
104+
else: return {
105+
"code": 202,
106+
"message": "Transaction still not approved."
107+
}, 202
108+
except KeyError: return {
109+
"code": 404,
110+
"message": "Verification code does not point to an active transaction.",
111+
"exception": "TransactionNotFound"
112+
}, 404
113+
except Exception as e: return {
114+
"code": 500,
115+
"message": f"Failed to process payment: {e}",
116+
"exception": type(e).__name__
117+
}, 500
118+
119+
@app.route('/account', methods=["GET"])
120+
def account():
121+
try:
122+
isocards = call_isocards_database()
123+
args = request.args
124+
isocard_number = args.get("cardnumber")
125+
ssc = args.get("ssc")
126+
if isocards[str(isocard_number)]["ssc"] == ssc:
127+
card_data = isocards[str(isocard_number)]
128+
del card_data["config"]
129+
del card_data["ssc"]
130+
card_data["account_balance"] = currency.get_wallet(card_data["user_id"])
131+
return {
132+
"code": 200,
133+
"card_info": card_data
134+
}, 200
135+
else: return {
136+
"code": 403,
137+
"message": "Incorrect IsoCard SSC.",
138+
"exception": "InvalidSSC"
139+
}
140+
except KeyError: return {
141+
"code": 404,
142+
"message": "Card number is invalid.",
143+
"exception": "InvalidIsoCard"
144+
}, 404
145+
except Exception as e: return {
146+
"code": 500,
147+
"message": f"Failed to fetch account info: {e}",
148+
"exception": type(e).__name__
149+
}
150+
151+
# Initialization
152+
def run(): app.run(host="0.0.0.0", port=4800)
153+
154+
if auth.get_runtime_options()["isocard_server_enabled"]: # Run server ONLY if its runtime option is enabled
155+
print("[isocard/server] Starting IsoCard payments server...")
156+
t = Thread(target=run)
157+
t.daemon = True
158+
t.start()
159+
160+
161+
#btw i use arch

main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from utils import logger, ping
1212
from math import floor
1313
from random import randint
14-
from framework.isobot import currency, colors, settings, commands as _commands
14+
from framework.isobot import currency, colors, settings, commands as _commands, isocard
1515
from framework.isobot.shop import ShopData
1616
from framework.isobot.db import levelling, items, userdata, automod, weather, warnings, presence as _presence, serverconfig, embeds
1717
from discord import ApplicationContext, option
@@ -54,6 +54,7 @@ def initial_setup():
5454
"user_data",
5555
"weather",
5656
"embeds",
57+
"isocard_transactions",
5758
"isobank/accounts",
5859
"isobank/auth"
5960
)

0 commit comments

Comments
 (0)