Skip to content

Commit 7ba6539

Browse files
authored
Add payment requests aka invoices (#87)
2 parents 87c7c07 + f920908 commit 7ba6539

File tree

11 files changed

+987
-4
lines changed

11 files changed

+987
-4
lines changed

src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.jetbrains.annotations.Nullable;
1818
import pro.cloudnode.smp.bankaccounts.commands.BaltopCommand;
1919
import pro.cloudnode.smp.bankaccounts.commands.BankCommand;
20+
import pro.cloudnode.smp.bankaccounts.commands.InvoiceCommand;
2021
import pro.cloudnode.smp.bankaccounts.commands.POSCommand;
2122
import pro.cloudnode.smp.bankaccounts.events.BlockBreak;
2223
import pro.cloudnode.smp.bankaccounts.events.GUI;
@@ -67,6 +68,7 @@ public void onEnable() {
6768
put("bank", new BankCommand());
6869
put("pos", new POSCommand());
6970
put("baltop", new BaltopCommand());
71+
put("invoice", new InvoiceCommand());
7072
}};
7173
for (Map.Entry<@NotNull String, @NotNull CommandExecutor> entry : commands.entrySet()) {
7274
final PluginCommand command = getCommand(entry.getKey());

src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java

Lines changed: 272 additions & 2 deletions
Large diffs are not rendered by default.
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
package pro.cloudnode.smp.bankaccounts;
2+
3+
import org.bukkit.OfflinePlayer;
4+
import org.jetbrains.annotations.NotNull;
5+
import org.jetbrains.annotations.Nullable;
6+
7+
import java.math.BigDecimal;
8+
import java.sql.Connection;
9+
import java.sql.PreparedStatement;
10+
import java.sql.ResultSet;
11+
import java.sql.SQLException;
12+
import java.sql.Types;
13+
import java.util.ArrayList;
14+
import java.util.Arrays;
15+
import java.util.Date;
16+
import java.util.List;
17+
import java.util.Optional;
18+
import java.util.UUID;
19+
import java.util.logging.Level;
20+
import java.util.stream.Collectors;
21+
22+
/**
23+
* A payment request
24+
*/
25+
public class Invoice {
26+
public final @NotNull String id;
27+
/**
28+
* The beneficiary
29+
*/
30+
public final @NotNull Account seller;
31+
public final @NotNull BigDecimal amount;
32+
private final @Nullable String description;
33+
34+
/**
35+
* A potential buyer/payer. If set, only they can see and pay the invoice. If `null`, the invoice is public.
36+
*/
37+
private final @Nullable OfflinePlayer buyer;
38+
public final @NotNull Date created;
39+
/**
40+
* Payment transaction. If set, the invoice is paid.
41+
*/
42+
public @Nullable Transaction transaction;
43+
44+
public Invoice(final @NotNull String id, final @NotNull Account seller, final @NotNull BigDecimal amount, final @Nullable String description, final @Nullable OfflinePlayer buyer, final @NotNull Date created, final @Nullable Transaction transaction) {
45+
this.id = id;
46+
this.seller = seller;
47+
this.amount = amount;
48+
this.description = description;
49+
this.buyer = buyer;
50+
this.created = created;
51+
this.transaction = transaction;
52+
}
53+
54+
public Invoice(final @NotNull Account seller, final @NotNull BigDecimal amount, final @Nullable String description, final @Nullable OfflinePlayer buyer) {
55+
this(StringGenerator.generate(16), seller, amount, description, buyer, new Date(), null);
56+
}
57+
58+
public Invoice(final @NotNull ResultSet rs) throws @NotNull SQLException {
59+
this(
60+
rs.getString("id"),
61+
Account.get(rs.getString("seller")).orElse(new Account.ClosedAccount()),
62+
rs.getBigDecimal("amount"),
63+
rs.getString("description"),
64+
rs.getString("buyer") == null ? null : BankAccounts.getInstance().getServer().getOfflinePlayer(UUID.fromString(rs.getString("buyer"))),
65+
new Date(rs.getTimestamp("created").getTime()),
66+
Transaction.get(rs.getInt("transaction")).orElse(null)
67+
);
68+
}
69+
70+
public @NotNull Optional<@NotNull String> description() {
71+
return Optional.ofNullable(description);
72+
}
73+
74+
public @NotNull Optional<@NotNull OfflinePlayer> buyer() {
75+
return Optional.ofNullable(buyer);
76+
}
77+
78+
public void pay(final @NotNull Account buyer) {
79+
transaction = buyer.transfer(seller, amount, "Invoice #" + id, null);
80+
update();
81+
}
82+
83+
public void insert() {
84+
try (final @NotNull Connection conn = BankAccounts.getInstance().getDb().getConnection();
85+
final @NotNull PreparedStatement stmt = conn.prepareStatement("INSERT INTO `bank_invoices` (`id`, `seller`, `amount`, `description`, `buyer`, `created`, `transaction`) VALUES (?, ?, ?, ?, ?, ?, ?)")) {
86+
stmt.setString(1, id);
87+
stmt.setString(2, seller.id);
88+
stmt.setBigDecimal(3, amount);
89+
if (description == null) stmt.setNull(4, java.sql.Types.VARCHAR);
90+
else stmt.setString(4, description);
91+
if (buyer == null) stmt.setNull(5, java.sql.Types.VARCHAR);
92+
else stmt.setString(5, buyer.getUniqueId().toString());
93+
stmt.setTimestamp(6, new java.sql.Timestamp(created.getTime()));
94+
if (transaction == null) stmt.setNull(7, java.sql.Types.INTEGER);
95+
else stmt.setInt(7, transaction.getId());
96+
97+
stmt.executeUpdate();
98+
} catch (final @NotNull SQLException e) {
99+
BankAccounts.getInstance().getLogger().log(Level.SEVERE, "Could not save invoice: " + id, e);
100+
}
101+
}
102+
103+
public void update() {
104+
try (final @NotNull Connection conn = BankAccounts.getInstance().getDb().getConnection();
105+
final @NotNull PreparedStatement stmt = conn.prepareStatement("UPDATE `bank_invoices` SET `transaction` = ? WHERE `id` = ?")) {
106+
if (transaction == null) stmt.setNull(1, Types.INTEGER);
107+
else stmt.setInt(1, transaction.getId());
108+
stmt.setString(2, id);
109+
110+
stmt.executeUpdate();
111+
} catch (final @NotNull SQLException e) {
112+
BankAccounts.getInstance().getLogger().log(Level.SEVERE, "Could not update invoice: " + id, e);
113+
}
114+
}
115+
116+
public static @NotNull Optional<@NotNull Invoice> get(final @NotNull String id) {
117+
try (final @NotNull Connection conn = BankAccounts.getInstance().getDb().getConnection();
118+
final @NotNull PreparedStatement stmt = conn.prepareStatement("SELECT * FROM `bank_invoices` WHERE `id` = ? LIMIT 1")) {
119+
stmt.setString(1, id);
120+
121+
final @NotNull ResultSet rs = stmt.executeQuery();
122+
123+
if (rs.next()) return Optional.of(new Invoice(rs));
124+
return Optional.empty();
125+
}
126+
catch (final @NotNull SQLException e) {
127+
BankAccounts.getInstance().getLogger().log(Level.SEVERE, "Could not get invoice: " + id, e);
128+
return Optional.empty();
129+
}
130+
}
131+
132+
public static @NotNull Invoice @NotNull [] get(final @NotNull OfflinePlayer player) {
133+
try (final @NotNull Connection conn = BankAccounts.getInstance().getDb().getConnection();
134+
final @NotNull PreparedStatement stmt = conn.prepareStatement("SELECT * FROM `bank_invoices` where `buyer` = ?")) {
135+
stmt.setString(1, player.getUniqueId().toString());
136+
final @NotNull ResultSet rs = stmt.executeQuery();
137+
138+
final @NotNull List<@NotNull Invoice> invoices = new ArrayList<>();
139+
while (rs.next()) invoices.add(new Invoice(rs));
140+
return invoices.toArray(new @NotNull Invoice[0]);
141+
}
142+
catch (final @NotNull SQLException e) {
143+
BankAccounts.getInstance().getLogger().log(Level.SEVERE, "Could not get invoices for player: " + player.getUniqueId(), e);
144+
return new @NotNull Invoice[0];
145+
}
146+
}
147+
148+
public static @NotNull Invoice @NotNull [] get(final @NotNull OfflinePlayer player, final int limit, final int offset) {
149+
try (final @NotNull Connection conn = BankAccounts.getInstance().getDb().getConnection();
150+
final @NotNull PreparedStatement stmt = conn.prepareStatement("SELECT * FROM `bank_invoices` where `buyer` = ? ORDER BY `created` DESC LIMIT ? OFFSET ?")) {
151+
stmt.setString(1, player.getUniqueId().toString());
152+
stmt.setInt(2, limit);
153+
stmt.setInt(3, offset);
154+
final @NotNull ResultSet rs = stmt.executeQuery();
155+
156+
final @NotNull List<@NotNull Invoice> invoices = new ArrayList<>();
157+
while (rs.next()) invoices.add(new Invoice(rs));
158+
return invoices.toArray(new @NotNull Invoice[0]);
159+
}
160+
catch (final @NotNull SQLException e) {
161+
BankAccounts.getInstance().getLogger().log(Level.SEVERE, "Could not get invoices for player: " + player.getUniqueId(), e);
162+
return new @NotNull Invoice[0];
163+
}
164+
}
165+
166+
public static @NotNull Invoice @NotNull [] get(final @NotNull OfflinePlayer player, final @NotNull Account @NotNull [] seller) {
167+
final @NotNull String inParams = Arrays.stream(seller).map(s -> "?").collect(Collectors.joining(", "));
168+
try (final @NotNull Connection conn = BankAccounts.getInstance().getDb().getConnection();
169+
final @NotNull PreparedStatement stmt = conn.prepareStatement("SELECT * FROM `bank_invoices` where `buyer` = ? OR `seller` IN (" + inParams + ")")) {
170+
stmt.setString(1, player.getUniqueId().toString());
171+
for (int i = 0; i < seller.length; ++i) stmt.setString(i + 2, seller[i].id);
172+
173+
final @NotNull ResultSet rs = stmt.executeQuery();
174+
final @NotNull List<@NotNull Invoice> invoices = new ArrayList<>();
175+
while (rs.next()) invoices.add(new Invoice(rs));
176+
return invoices.toArray(new Invoice[0]);
177+
}
178+
catch (final @NotNull SQLException e) {
179+
BankAccounts.getInstance().getLogger().log(Level.SEVERE, "Could not get invoices for buyer & seller: " + player.getUniqueId(), e);
180+
return new @NotNull Invoice[0];
181+
}
182+
}
183+
184+
public static @NotNull Invoice @NotNull [] get(final @NotNull OfflinePlayer player, final @NotNull Account @NotNull [] seller, final int limit, final int offset) {
185+
final @NotNull String inParams = Arrays.stream(seller).map(s -> "?").collect(Collectors.joining(", "));
186+
try (final @NotNull Connection conn = BankAccounts.getInstance().getDb().getConnection();
187+
final @NotNull PreparedStatement stmt = conn.prepareStatement("SELECT * FROM `bank_invoices` where `buyer` = ? OR `seller` IN (" + inParams + ") ORDER BY `created` DESC LIMIT ? OFFSET ?")) {
188+
stmt.setString(1, player.getUniqueId().toString());
189+
for (int i = 0; i < seller.length; ++i) stmt.setString(i + 2, seller[i].id);
190+
stmt.setInt(seller.length + 2, limit);
191+
stmt.setInt(seller.length + 3, offset);
192+
193+
final @NotNull ResultSet rs = stmt.executeQuery();
194+
final @NotNull List<@NotNull Invoice> invoices = new ArrayList<>();
195+
while (rs.next()) invoices.add(new Invoice(rs));
196+
return invoices.toArray(new Invoice[0]);
197+
}
198+
catch (final @NotNull SQLException e) {
199+
BankAccounts.getInstance().getLogger().log(Level.SEVERE, "Could not get invoices for buyer & seller: " + player.getUniqueId(), e);
200+
return new @NotNull Invoice[0];
201+
}
202+
}
203+
204+
public static @NotNull Invoice @NotNull [] get(final @NotNull Account @NotNull [] seller, final int limit, final int offset) {
205+
final @NotNull String inParams = Arrays.stream(seller).map(s -> "?").collect(Collectors.joining(", "));
206+
try (final @NotNull Connection conn = BankAccounts.getInstance().getDb().getConnection();
207+
final @NotNull PreparedStatement stmt = conn.prepareStatement("SELECT * FROM `bank_invoices` where `seller` IN (" + inParams + ") ORDER BY `created` DESC LIMIT ? OFFSET ?")) {
208+
for (int i = 0; i < seller.length; ++i) stmt.setString(i + 1, seller[i].id);
209+
stmt.setInt(seller.length + 1, limit);
210+
stmt.setInt(seller.length + 2, offset);
211+
212+
final @NotNull ResultSet rs = stmt.executeQuery();
213+
final @NotNull List<@NotNull Invoice> invoices = new ArrayList<>();
214+
while (rs.next()) invoices.add(new Invoice(rs));
215+
return invoices.toArray(new Invoice[0]);
216+
}
217+
catch (final @NotNull SQLException e) {
218+
BankAccounts.getInstance().getLogger().log(Level.SEVERE, "Could not get invoices for seller: " + Arrays.toString(seller), e);
219+
return new @NotNull Invoice[0];
220+
}
221+
}
222+
223+
public static @NotNull Invoice @NotNull [] get() {
224+
try (final @NotNull Connection conn = BankAccounts.getInstance().getDb().getConnection();
225+
final @NotNull PreparedStatement stmt = conn.prepareStatement("SELECT * FROM `bank_invoices`")) {
226+
final @NotNull ResultSet rs = stmt.executeQuery();
227+
228+
final @NotNull List<@NotNull Invoice> invoices = new ArrayList<>();
229+
while (rs.next()) invoices.add(new Invoice(rs));
230+
return invoices.toArray(new @NotNull Invoice[0]);
231+
}
232+
catch (final @NotNull SQLException e) {
233+
BankAccounts.getInstance().getLogger().log(Level.SEVERE, "Could not get invoices", e);
234+
return new @NotNull Invoice[0];
235+
}
236+
}
237+
}

src/main/java/pro/cloudnode/smp/bankaccounts/Permissions.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ public final class Permissions {
1717
public static @NotNull String BALTOP = "bank.baltop";
1818
public static @NotNull String POS_CREATE = "bank.pos.create";
1919
public static @NotNull String POS_USE = "bank.pos.use";
20+
public static @NotNull String INVOICE_CREATE = "bank.invoice.create";
21+
public static @NotNull String INVOICE_CREATE_OTHER = "bank.invoice.create.other";
22+
public static @NotNull String INVOICE_VIEW = "bank.invoice.view";
23+
public static @NotNull String INVOICE_VIEW_OTHER = "bank.invoice.view.other";
24+
public static @NotNull String INVOICE_PAY_OTHER = "bank.invoice.pay.other";
25+
public static @NotNull String INVOICE_PAY_ACCOUNT_OTHER = "bank.invoice.pay.account-other";
26+
public static @NotNull String INVOICE_SEND = "bank.invoice.send";
27+
public static @NotNull String INVOICE_SEND_OTHER = "bank.invoice.send.other";
2028
public static @NotNull String RELOAD = "bank.reload";
2129
public static @NotNull String BALANCE_OTHER = "bank.balance.other";
2230
public static @NotNull String HISTORY_OTHER = "bank.history.other";

src/main/java/pro/cloudnode/smp/bankaccounts/Transaction.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.util.ArrayList;
1313
import java.util.Date;
1414
import java.util.List;
15+
import java.util.Optional;
1516
import java.util.logging.Level;
1617

1718
/**
@@ -121,6 +122,22 @@ public void save() {
121122
}
122123
}
123124

125+
/**
126+
* Get transaction by ID
127+
* @param id Transaction ID
128+
*/
129+
public static Optional<Transaction> get(int id) {
130+
try (Connection conn = BankAccounts.getInstance().getDb().getConnection();
131+
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM `bank_transactions` WHERE `id` = ? LIMIT 1")) {
132+
stmt.setInt(1, id);
133+
ResultSet rs = stmt.executeQuery();
134+
return rs.next() ? Optional.of(new Transaction(rs)) : Optional.empty();
135+
} catch (Exception e) {
136+
BankAccounts.getInstance().getLogger().log(Level.SEVERE, "Could not get transaction: " + id, e);
137+
return Optional.empty();
138+
}
139+
}
140+
124141
/**
125142
* Get ALL transactions of account
126143
* @param account Account

src/main/java/pro/cloudnode/smp/bankaccounts/commands/BankCommand.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Objects;
2222
import java.util.Optional;
2323
import java.util.stream.Collectors;
24+
import java.util.stream.Stream;
2425

2526
public class BankCommand extends Command {
2627
@Override
@@ -175,7 +176,7 @@ public static boolean run(final @NotNull CommandSender sender, final @NotNull St
175176
case "instrument", "card" -> instrument(sender, argsSubset, label);
176177
case "whois", "who", "info" -> whois(sender, argsSubset, label);
177178
case "baltop" -> baltop(sender, argsSubset, label);
178-
default -> sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsUnknownCommand());
179+
default -> sendMessage(sender, BankAccounts.getInstance().config().messagesErrorsUnknownCommand(label));
179180
};
180181
}
181182

@@ -228,6 +229,8 @@ public static boolean help(final @NotNull CommandSender sender) {
228229
sendMessage(sender, "<click:suggest_command:/bank setname ><green>/bank setname <gray><account> [name]</gray></green> <white>- Set an account's name</click>");
229230
if (sender.hasPermission(Permissions.RELOAD))
230231
sendMessage(sender, "<click:suggest_command:/bank reload><green>/bank reload</green> <white>- Reload plugin configuration</click>");
232+
if (Stream.of(Permissions.INVOICE_CREATE, Permissions.INVOICE_VIEW, Permissions.INVOICE_SEND, Permissions.TRANSFER_SELF, Permissions.TRANSFER_OTHER).anyMatch(sender::hasPermission))
233+
sendMessage(sender, "<click:suggest_command:/invoice help><green>/invoice help</green> <white>- See invoicing commands</click>");
231234
return sendMessage(sender, "<dark_gray>---</dark_gray>");
232235
}
233236

0 commit comments

Comments
 (0)