server - Handle cokebank errors when dispensing
[tpg/opendispense2.git] / src / server / dispense.c
1 /**
2  */
3 #include "common.h"
4 #include <stdlib.h>
5 #include <limits.h>
6 #include <string.h>
7
8  int    _GetMinBalance(int Account);
9  int    _CanTransfer(int Source, int Destination, int Ammount);
10  int    _Transfer(int Source, int Destination, int Ammount, const char *Reason);
11  int    _GetSalesAcct(tItem *Item);
12
13 // === CODE ===
14 /**
15  * \brief Dispense an item for a user
16  * 
17  * The core of the dispense system, I kinda like it :)
18  */
19 int DispenseItem(int ActualUser, int User, tItem *Item)
20 {
21          int    ret, salesAcct;
22         tHandler        *handler;
23         char    *username, *actualUsername;
24         
25         handler = Item->Handler;
26         
27         salesAcct = _GetSalesAcct(Item);
28
29         // Check if the user can afford it
30         if( Item->Price && !_CanTransfer(User, salesAcct, Item->Price) )
31         {
32                 return 2;       // 2: No balance
33         }
34         
35         // HACK: Naming a slot "dead" disables it
36         if( strcmp(Item->Name, "dead") == 0 )
37                 return 1;
38         
39         // Check if the dispense is possible
40         if( handler->CanDispense ) {
41                 ret = handler->CanDispense( User, Item->ID );
42                 if(ret) return 1;       // 1: Unable to dispense
43         }
44         
45         // Get username for debugging
46         username = Bank_GetAcctName(User);
47         
48         // Ordering: Pay, Drop. Worst case requires a refund, other ordering leads to drops when payment fails.
49         
50         // Take away money
51         if( Item->Price )
52         {
53                 char    *reason;
54                 reason = mkstr("Dispense - %s:%i %s", handler->Name, Item->ID, Item->Name);
55                 if( _Transfer( User, salesAcct, Item->Price, reason ) != 0 ) {
56                         Log_Error("Dispense failed (%s dispensing %s:%i '%s') - Cokebank error!",
57                                 username, Item->Handler->Name, Item->ID, Item->Name);
58                         free(reason);
59                         free( username );
60                         return -1;      // -1: Unknown error
61                 }
62                 free(reason);
63         }
64         
65         // Actually do the dispense
66         if( handler->DoDispense ) {
67                 ret = handler->DoDispense( User, Item->ID );
68                 if(ret) {
69                         Log_Error("Dispense failed (%s dispensing %s:%i '%s')",
70                                 username, Item->Handler->Name, Item->ID, Item->Name);
71                         free( username );
72                         return -1;      // -1: Unknown Error
73                 }
74         }
75         
76         actualUsername = Bank_GetAcctName(ActualUser);
77         
78         // And log that it happened
79         if( gbNoCostMode )
80         {
81                 // Special format for zero cost dispenses
82                 Log_Info("test dispense '%s' (%s:%i) for %s by %s [no change]",
83                         Item->Name, handler->Name, Item->ID,
84                         username, actualUsername
85                         );
86         }
87         else
88         {
89                 Log_Info("dispense '%s' (%s:%i) for %s by %s [cost %i, balance %i]",
90                         Item->Name, handler->Name, Item->ID,
91                         username, actualUsername, Item->Price, Bank_GetBalance(User)
92                         );
93         }
94         
95         free( username );
96         free( actualUsername );
97         return 0;       // 0: EOK
98 }
99
100 /**
101  * \brief Refund a dispense
102  */
103 int DispenseRefund(int ActualUser, int DestUser, tItem *Item, int OverridePrice)
104 {
105          int    ret;
106          int    src_acct, price;
107         char    *username, *actualUsername;
108
109         src_acct = _GetSalesAcct(Item);
110
111         if( OverridePrice > 0 )
112                 price = OverridePrice;
113         else
114                 price = Item->Price;
115
116         ret = _Transfer( src_acct, DestUser, price, "Refund");
117         if(ret) return ret;
118
119         username = Bank_GetAcctName(DestUser);
120         actualUsername = Bank_GetAcctName(ActualUser);
121         
122         Log_Info("refund '%s' (%s:%i) to %s by %s [cost %i, balance %i]",
123                 Item->Name, Item->Handler->Name, Item->ID,
124                 username, actualUsername, price, Bank_GetBalance(DestUser)
125                 );
126
127         free(username);
128         free(actualUsername);
129
130         return 0;
131 }
132
133 /**
134  * \brief Give money from one user to another
135  */
136 int DispenseGive(int ActualUser, int SrcUser, int DestUser, int Ammount, const char *ReasonGiven)
137 {
138          int    ret;
139         char    *actualUsername;
140         char    *srcName, *dstName;
141         
142         // HACK: Naming a slot "dead" disables it (catch for snack)
143         if( strcmp(ReasonGiven, "dead") == 0 )
144                 return 1;
145         
146         if( Ammount < 0 )       return 1;       // Um... negative give? Not on my watch!
147         
148         ret = _Transfer( SrcUser, DestUser, Ammount, ReasonGiven );
149         if(ret) return 2;       // No Balance
150         
151         
152         actualUsername = Bank_GetAcctName(ActualUser);
153         srcName = Bank_GetAcctName(SrcUser);
154         dstName = Bank_GetAcctName(DestUser);
155         
156         Log_Info("give %i from %s to %s by %s [balances %i, %i] - %s",
157                 Ammount, srcName, dstName, actualUsername,
158                 Bank_GetBalance(SrcUser), Bank_GetBalance(DestUser),
159                 ReasonGiven
160                 );
161         
162         free(srcName);
163         free(dstName);
164         free(actualUsername);
165         
166         return 0;
167 }
168
169 #if 0 // Dead Code
170 /**
171  * \brief Move money from one user to another (Admin Only)
172  */
173 int DispenseTransfer(int ActualUser, int SrcUser, int DestUser, int Ammount, const char *ReasonGiven)
174 {
175          int    ret;
176         char    *actualUsername;
177         char    *srcName, *dstName;
178
179         // Make sure the user is an admin
180         if( !(Bank_GetFlags(ActualUser) & USER_FLAG_ADMIN) )
181                 return 1;
182         
183         ret = _Transfer( SrcUser, DestUser, Ammount, ReasonGiven );
184         if(ret) return 2;       // No Balance
185         
186         
187         actualUsername = Bank_GetAcctName(ActualUser);
188         srcName = Bank_GetAcctName(SrcUser);
189         dstName = Bank_GetAcctName(DestUser);
190         
191         Log_Info("move %i from %s to %s by %s [balances %i, %i] - %s",
192                 Ammount, srcName, dstName, actualUsername,
193                 Bank_GetBalance(SrcUser), Bank_GetBalance(DestUser),
194                 ReasonGiven
195                 );
196         
197         free(srcName);
198         free(dstName);
199         free(actualUsername);
200         
201         return 0;
202 }
203 #endif
204
205 /**
206  * \brief Add money to an account
207  */
208 int DispenseAdd(int ActualUser, int User, int Ammount, const char *ReasonGiven)
209 {
210          int    ret;
211         char    *dstName, *byName;
212         
213 #if DISPENSE_ADD_BELOW_MIN
214         ret = _Transfer( Bank_GetAcctByName(COKEBANK_ADDSRC_ACCT,1), User, Ammount, ReasonGiven );
215 #else
216         ret = Bank_Transfer( Bank_GetAcctByName(COKEBANK_ADDSRC_ACCT,1), User, Ammount, ReasonGiven );
217 #endif
218         if(ret) return 2;
219         
220         byName = Bank_GetAcctName(ActualUser);
221         dstName = Bank_GetAcctName(User);
222         
223         Log_Info("add %i to %s by %s [balance %i] - %s",
224                 Ammount, dstName, byName, Bank_GetBalance(User), ReasonGiven
225                 );
226         
227         free(byName);
228         free(dstName);
229         
230         return 0;
231 }
232
233 int DispenseSet(int ActualUser, int User, int Balance, const char *ReasonGiven, int *OrigBalance)
234 {
235          int    curBal = Bank_GetBalance(User);
236         char    *byName, *dstName;
237         
238         if( Bank_Transfer( Bank_GetAcctByName(COKEBANK_DEBT_ACCT,1), User, Balance-curBal, ReasonGiven ) )
239                 return -1;
240         
241         byName = Bank_GetAcctName(ActualUser);
242         dstName = Bank_GetAcctName(User);
243         
244         Log_Info("set balance of %s to %i by %s [was %i, balance %i] - %s",
245                 dstName, Balance, byName, curBal, Bank_GetBalance(User), ReasonGiven
246                 );
247         
248         *OrigBalance = curBal;
249         free(byName);
250         free(dstName);
251         
252         return 0;
253 }
254
255 /**
256  * \brief Donate money to the club
257  */
258 int DispenseDonate(int ActualUser, int User, int Ammount, const char *ReasonGiven)
259 {
260          int    ret;
261         char    *srcName, *byName;
262         
263         if( Ammount < 0 )       return 2;
264         
265         ret = _Transfer( User, Bank_GetAcctByName(COKEBANK_DONATE_ACCT,1), Ammount, ReasonGiven );
266         if(ret) return 2;
267         
268         byName = Bank_GetAcctName(ActualUser);
269         srcName = Bank_GetAcctName(User);
270         
271         Log_Info("donate %i from %s by %s [balance %i] - %s",
272                 Ammount, srcName, byName, Bank_GetBalance(User), ReasonGiven
273                 );
274         
275         free(byName);
276         free(srcName);
277         
278         return 0;
279 }
280
281 int DispenseUpdateItem(int User, tItem *Item, const char *NewName, int NewPrice)
282 {
283         char    *username;
284         
285         // Sanity checks
286         if( NewPrice < 0 )      return 2;
287         if( !Item )     return 2;
288         if( strlen(NewName) < 1 )       return 2;
289         
290         // Update the item
291         free(Item->Name);
292         Item->Name = strdup(NewName);
293         Item->Price = NewPrice;
294         
295         username = Bank_GetAcctName(User);
296         
297         Log_Info("item %s:%i updated to '%s' %i by %s",
298                 Item->Handler->Name, Item->ID,
299                 NewName, NewPrice, username
300                 );
301         
302         free(username);
303         
304         // Update item file
305         Items_UpdateFile();
306         
307         return 0;
308 }
309
310 // --- Internal Functions ---
311 int _GetMinBalance(int Account)
312 {
313          int    flags = Bank_GetFlags(Account);
314         
315         // Evil little piece of HACK:
316         // root's balance cannot be changed by any of the above functions
317         // - Stops dispenses as root by returning insufficent balance.
318         {
319                 char    *username = Bank_GetAcctName(Account);
320                 if( strcmp(username, "root") == 0 )
321                 {
322                         free(username);
323                         return INT_MAX;
324                 }
325                 free(username);
326         }
327         
328         // - Internal accounts have no lower bound
329         if( flags & USER_FLAG_INTERNAL )        return INT_MIN;
330         
331         // Admin to -$50
332 //      if( flags & USER_FLAG_ADMIN )   return -5000;
333         
334         // Coke to -$20
335 //      if( flags & USER_FLAG_COKE )    return -2000;
336         
337         // Anyone else, non-negative
338         return 0;
339 }
340
341 /**
342  * \brief Check if a transfer is possible
343  * \return Boolean success
344  */
345 int _CanTransfer(int Source, int Destination, int Ammount)
346 {
347 //      if( Bank_GetFlags(Source) & USER_FLAG_DISABLED )
348 //              return 0;
349         if( Ammount > 0 )
350         {
351                 if( Bank_GetBalance(Source) - Ammount < _GetMinBalance(Source) )
352                         return 0;
353         }
354         else
355         {
356                 if( Bank_GetBalance(Destination) + Ammount < _GetMinBalance(Destination) )
357                         return 0;
358         }
359         return 1;
360 }
361
362 int _Transfer(int Source, int Destination, int Ammount, const char *Reason)
363 {
364         if( !_CanTransfer(Source, Destination, Ammount) )
365                 return 1;
366         return Bank_Transfer(Source, Destination, Ammount, Reason);
367 }
368
369 int _GetSalesAcct(tItem *Item)
370 {
371         char string[sizeof(COKEBANK_SALES_PREFIX)+strlen(Item->Handler->Name)];
372         strcpy(string, COKEBANK_SALES_PREFIX);
373         strcat(string, Item->Handler->Name);
374         return Bank_GetAcctByName(string, 1);
375 }

UCC git Repository :: git.ucc.asn.au