Jump to content
MakeWebGames

Inveteratus

Members
  • Posts

    57
  • Joined

  • Last visited

  • Days Won

    11

Inveteratus last won the day on August 27

Inveteratus had the most liked content!

Personal Information

  • Location
    UK

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

Inveteratus's Achievements

Enthusiast

Enthusiast (6/14)

  • One Year In
  • Reacting Well Rare
  • One Month Later
  • Collaborator Rare
  • Week One Done

Recent Badges

38

Reputation

  1. So, the rebalancing went through almost as expected. There were a couple of minor hiccups that I hadn't anticipated and, in all honesty, I'm still trying to work out exactly how they happened. Thankfully, neither is damaging to the game. One was a very easy fix; the other... well, I think I'm just going to leave it as-is. It affects a fair few players, certainly, but it's not really a major concern. The rebalancing was potentially a risky one. If you look back at the original MCCodes codebase, there are quite a few design errors that, often through inexperience on the part of owners and admins, have been propagated into many games that are still being played today. Some of those problems were sadly the result of the original developers not fully considering the long-term ramifications of what they were writing. But while blaming them might be easy, it's probably not particularly useful either. They built something that lasted, after all. For a game to really work, a surprising amount of time and investment has to go into it. Code, data, user interface, graphics, setting and story all play crucial roles. They're often the difference between a game that's merely playable and earns a few quid, and one that's actually enjoyable — a game with challenges that keep players engaged and enough depth to give it some longevity. I've only solved a few of those points! The legacy game I took over now has a much better codebase and a far more sensible dataset. The challenges, though... I'm still working on those. Getting the right staff is oddly one of the problems. The current staff are good, but there are probably areas where we're still lacking. The story is almost non-existent, which is something that really needs addressing, and many of the challenges within the game are still relatively minor. Oddly enough, during this whole process I started creating another game — but this time I approached it completely differently. I started with the story, the setting, the graphics and the challenges. The code then fell into place remarkably easily, and the data followed naturally afterwards. It's not released yet, but building it this way has made me wonder whether some of its ideas could be pushed back into the old legacy game. More cooperative play, for example, is something I'd really like to experiment with. I often wonder where this game will eventually go. It earns a small income each month — certainly enough to cover the hosting costs, licence fees, AI subscriptions and the other bits and pieces needed to keep it running. It might even pay the mortgage in due course! But I increasingly find myself asking a different question: Where do I actually want it to go? It has taken an enormous amount of time and effort over the last nine months, and I'm not entirely sure my heart is still in it in quite the same way. What I really need is a few good people who can help run it. People with the vision to generate ideas, keep the players engaged, challenge my decisions and tell me when and where I've got something wrong. But then... if the game is producing a passable income, why not simply accept it for what it is? Maybe this is the point many developers eventually reach. Do we continue developing something that has gradually become more labour and less love? Or do we decide we've taken it as far as we want to and sell it onwards to somebody with fresh enthusiasm? I genuinely don't know how to answer that yet. One thing I do know is that running a game like this will always be an ongoing process. If, as a developer, you neglect the game and simply spend the income, eventually the players will notice. They'll become frustrated, they'll start leaving, and the game will slowly decline. If you're prepared to keep putting that extra effort in, though, perhaps you'll be one of the lucky ones who ends up with a game that survives the test of time. Me? I think I'll take the occasional holiday, keep tinkering with things, and keep the game running for as long as both the players and I still have some interest in it. Hosting is cheap. Licences aren't too painful. The subscriptions... well, hopefully there won't be quite so much need for those in due course. So that leaves just one question: What shall we do next?!
  2. I was recently asked to help out with an old MCCodes game that had been running for a good ten years or so. I was quite happy to take it on; it had been a long time since I'd played around with that particular codebase, and I thought it might make a pleasant change to see what had been done with it over the years, particularly after passing through the hands of several different developers. Oh dear. There were problems. Lots of problems. Nothing insurmountable, fortunately, but it quickly became apparent that this wasn't going to be a case of fixing a few bugs and tidying up some CSS. The game was still largely running on the old procedural MCCodes architecture, with pretty much everything sitting in the public directory. Business logic, database queries and presentation were thoroughly intertwined. The first job, therefore, wasn't really to fix anything at all—it was to understand exactly what was there and work out how to drag it into something resembling a modern application without breaking a game people were actively playing. After analysing the codebase, I decided to target the Slim framework. It's small, straightforward to learn, uses PSR request/response interfaces and provides proper middleware support. No, it's certainly not Laravel. There are no bells and whistles and the batteries are most definitely not included. As it turned out, that was actually a good thing. It took a couple of months and a few false starts before I was completely convinced, but eventually it became clear that this was the right approach. Then began the long process of converting the original procedural files into a Controller / Repository / Template structure. That relatively simple architectural change made an enormous difference. Database access was moved into repository classes. Application logic moved into controllers. Presentation moved into templates. Suddenly the different parts of the application had clearly defined responsibilities, and changing the appearance of the game no longer meant risking damage to the underlying game logic. AI helped speed up some of that process considerably, although a surprising amount was still ported by hand. I wanted to retain control over the architecture and, more importantly, make sure the project was moving in the direction I wanted rather than simply translating old code into newer-looking old code. Somewhere along the way, things took another unexpected turn: I ended up buying the game. Being a co-developer is fine. Owning the project yourself is considerably better. That gave me much greater freedom to address some of the deeper problems rather than continually working around them. One of the first major additions was a completely new logging system. Rather than recording only obvious events, it tracks virtually every significant action performed by players. That has proved invaluable for finding those awkward last-minute bugs where somebody reports that "something strange happened" but can't quite remember what they did beforehand. Alongside that came a much more capable multi-account detection system, and that has produced some particularly interesting results. It can identify obvious same-IP transfers, but also patterns involving feeder accounts, indirect transfers through several players, suspicious account relationships and activity hidden behind VPNs. Combining that information with general player activity has made it possible to identify bots, multis, feeders and outright cheating—as well as cases where players weren't necessarily cheating at all, but had discovered and exploited flaws in the game's own logic. And there were plenty of those flaws to discover. The final major addition to the development process was something the original game had never possessed: a comprehensive automated test suite. Code coverage went from effectively zero to approximately 98.5%. That has completely changed the way the game can be developed. Changes that previously required a great deal of manual testing can now be made with considerably more confidence. The tests don't prove that bugs are impossible, of course, but they provide an extremely effective safety net and quickly highlight when a change in one part of the game has unexpectedly affected another. Static analysis also proved invaluable during the early stages. I believe I eventually cleared more than 5,000 warnings, errors and questionable pieces of code from the original codebase. Static analysis is excellent at finding structural problems, dubious assumptions and outright programming mistakes; unit and feature tests then provide confidence that the behaviour of the application remains correct as it continues to evolve. And that brings me to perhaps the biggest problem of all. Ten years is a long time for an online game. Over those years, a considerable number of mistakes had accumulated. Some came from MCCodes itself, whose underlying design is badly flawed in the way it handles several important aspects of the game economy and player progression. Others came from developers with limited experience blindly copying existing ideas or implementing player requests without considering the long-term consequences. Some of those mistakes were relatively harmless. Others allowed enormous and completely unintended gains in experience, skills and money. The result is a game population with a very unusual distribution. The majority of players occupy the lower end of the progression curve, while a relatively small number sit right at the extreme upper limit—sometimes effectively clamped against values that were never sensibly designed to be reached. Simply resetting those players isn't reasonable. Leaving everything untouched isn't particularly viable either. So the next major job is rebalancing the game's statistics and progression system. This is considerably more complicated than merely dividing everyone's stats by ten. There are numerous relationships and dependencies that have to be preserved, along with a large number of checks that need to be performed. The objective is to compress the inflated values while ensuring that players retain their relative positions. Somebody who has legitimately worked their way into the top 1% should remain in approximately that position afterwards. The difference is that they'll once again have somewhere to go. Development testing has shown that this approach works extremely well. Skills, experience and levels return to manageable ranges, while the relative hierarchy between players remains intact. More importantly, it opens up enormous scope for future development. We can introduce more jobs, courses, houses, crimes and other progression systems. Existing high-level players can finally receive meaningful new content, while newer players have a realistic path towards eventually reaching those same heights. And, critically, the underlying mathematics can now be designed so that uncontrolled stat inflation simply cannot happen again. So what have I learned from taking over a decade-old browser game? Never assume the code is the whole story. Analyse the code, certainly, but analyse the data just as carefully. Understand how the game has actually been used rather than merely how its code suggests it should have been used. Understand your players and the economy they've created. Introduce logging early. Get static analysis running. Build unit and feature tests as quickly as possible. Look for inconsistencies and investigate the strange outliers rather than dismissing them. Because sometimes those outliers tell you far more about the health of the game than the code ever will. In my case, one of those investigations revealed that a single player held approximately a quarter of all the money in the entire game. At that point, you stop wondering whether the economy might need a little attention.
  3. As I have a project that is rapidly reaching GA, I'd like to improve the incentive voting systems. I currently have 4 sites available only one of which appears to offer a callback providing a guaranteed "voted" mechanism. I'm really looking only for sites that provide a callback mechanism, and for reference, I'm currently using: topgamesites.net, mpogtop.com, mmohub.com, and mmorpg50.net Are these sites still of use, and if so what experiences do you have of them? Any sites you would recommend?
  4. lmao - nicely said.
  5. One is sorely tempted to respond with simply an image of a bag of humbugs, but at my age, I like a humbug or two! Satan Santa has certainly brought me some gifts which I must admit to being rather pleased with - I hope you all did as well. As for the new year, I hope you all have a decent one - I know I will 😄
  6. Stats ? Active Players ? Income ?
  7. Actually I never got around to them - was enjoying the 70's vibe of the background too much 😉
  8. Looks to be up - albeit a simple and rather loud placeholder! Almost had me reaching for the shades 🙂
  9. Melvor Idle Forge of Empires
  10. Hmm, 2016 yet apparently no backups. I swear I was making backups in the, admittedly late, 70s - I guess that process must have skipped a generation or two! Mind you, had the most recent poster noticed the date (Sep 2016), perhaps they would not have not been too concerned about demos or images!
  11. Well, I considered doing another deep dive into some of the fun bits of Mccodes, maybe looking at the job system and seeing how to improve it, or looking into the very poor casino games and seeing what could be done to turn them around, but I kept hitting stumbling blocks. When the code was open-sourced, I think lots of people including myself have high hopes of many people starting to turn it around, but what we see is very little work on the code if anything. Cronless crons and a role-based access control system, both of which are frankly pretty poor bits of work - I'd suggest ripping them out and returning to the old v2.0.5b base. At least we all know the problems with it! For reference, running static analysis tools on these modules shows a wealth of problems, so much so that I'm not going to examine them in detail. No deep dive, however does not mean the end of my foray into the world of Mccodes, far from it. I've started from the open-source edition, and have: Introduced a full template system using Blade as the template language. Created a front-end controller to direct new code into name-spaced controllers rather than individual files in the public folder. Added repository classes so controllers talk to the database only through the repositories, make it easier to change database back-end, table and field names. Used PDO as the core database driver rather than MySQL(i). Improved the docker stack considerably - though to be fair, it's for local development, not production. Rebuilt a number of pages using the new template system which allows for easy integration with the old code. Used Tailwind CSS as the CSS framework to get up and running quickly. Ensured the controllers are all PSR7 compliant, so dropping middleware in (for guest/auth redirection, and performing cron tasks) will be simplified. Started to flesh out the houses, crimes, jobs, cities, courses and items with fixtures as a "get you started" set of data. Used PHP 8.3 in the docker stack though I aim to get 8.4 in shortly, and MySQL 8 is also present along with Adminer for simple database management. Created a routing system for the front-end controller ensuring that all new code is defined by the routes. Checked the code regularly with static analysis tools to ensure no new bugs (or features as we used to call them) are brought into the system. Used Alpine JS to enhance the site to help with things like tabbed controls, show/hide password controls, modals etc. Stripped out the logging and replaced with Monolog with a database adapter. There's a lot of items on my roadmap: Finish of the core code rewrites to address all the common modules available from the menu and explore screens. Redo the crimes formula system. Refactor the attacking code to simplify it and better address how weapons can be used. Increase the amount of equipment slots for armor - thinking legs, body, hands, feet and head here. Use Alpine JS to enhance the site to help with things like tabbed controls, show/hide password controls, modals etc. Bring in some modules including the stock market, auction rooms foraging, and some as yet unnamed one. Add tests with PestPHP. Ensure where necessary, complex updates are handled within transactions to ensure atomicity. Documentation! Images for the relevant fixtures - cities, houses, etc. Visually, it does look at lot better, the templates allow me to get rid of the one-page lag often seen when committing crimes or training in the gym, but I've tried to retain the original look and feel to a certain extent - at least for now. At some stage, I intend forking my own work to spin up a game, though I pull push what I have online as soon as possible as a playable demo. During development, it will be refreshed on a regular basis until all the modules are rewritten to ensure none of the older code causes problems.
      • 1
      • Like
  12. Some very good suggestions - culling inactive actives is thoroughly recommended though I'd be inclined to mail them 30 days in advance of a cull with the option to opt-out - they may be unable to play temporarily after all. I think I like the idea of only earning interest up to a set amount; it would help keep a cap on games where the interest rate has been set high, which may be deliberate. Charges, I've considered but I've never found a satisfactory mechanism that allows me to bleed funds out as a standalone measure - it seems too much like taxes in real life which I usually try to avoid ... and in real life! I suspect the key point to take from these discussions is that balance is very important. And while you won't manage that with an out-of-the-box system, with careful play-testing and no doubt hours slaving over a few spreadsheets will result in a game that lasts. There will always be areas that need tuning up, but if you get the basics right, the fine tuning can be done over time.
  13. So what if I told you how to make $135,259 just by sitting on your hands just by giving me $100? Or how about if you give me $100 and I will show you how to make $5,323,942,470,216 again just by sitting on your hands? I've no doubt at this point, you are probably reaching for the "Report Spam" button, but bear with me a moment .. This is how MCCodes games are designed from the get-go. There's no need to play the game, just sit and wait and you will soon be richer than ... well everyone! Okay, some explanation .. lets look at a couple of critical lines in MCCodes in the cron_day.php script. UPDATE users SET bankmoney = bankmoney + bankmoney / 50, cybermoney = cybermoney + cybermoney / 100 * 7 Will that doesn't seem so bad .. or does it? Since MySQL will be rounding the result so if we look at the basic bank to start with given you save $100: After 1 day you will have $102, after 2 days, $104... even after 10 days you will only have $120. Weill again, that doesn't seem to bad. It's not going to introduce too much money into the system - after all $20 in 10 days is peanuts. But wait, after 20 days, it's risen to $147, after 30 $177. After 50 we are up to $265, after 100, $713. In fact, its doubling your initial money every 36 days or so. So after 1 year,. you will have $135,359 in the bank for an outlay of only $100! I wish I got that in the high street! Before we look at the frankly frightening cyber-bank, a little math. $principal = 100; $futureValue = 135359; $annualPercentageRate = $principal * $futureValue / 100 + $principal; Yipes - that's a rate of 135,259% Now for those of you paying mortgages, you may be lucky to have a rate as low as 2.5%, I believe I'm paying around 4.5% at the moment, while my bank is giving me a meager 3.3% rate on my savings. So what about the cyber bank. This one is truly scary. It doubles your money every 10.5 days ish. So in 10 days you will have amassed $197, in 20 days, that's gone up to $388, in 50 days we have a rather nice $2,953, while in 100 days we've got $93,092. These figures a truly scary! And after a year... Well we would have $5,323,942,470,316 but as that overflows the data-type in MySQL (a signed int) we would have the maximum of $2,147,483,647! Phew! Well clearly, the banks calculations are bringing in far too much money into the game, so what can be done about it? Well the first thing I suggest is to shut down the cyber-bank. It might be a nice idea, but it's going to ruin your game in double quick time. Since it's doubling the principal every 10.5 days, then every years, the amount of cash in the game will be fatal. With 10 players not a major problem, but with 10 players joining every month .. you have a financial crisis on your hands in well under a year. Next suggestion is drastic. Remove the bank-money and cyber-money update statements from your daily cron. In fact you don't need crons to work out what your bank value will be - again it's all down to a simple bit of math. (We'll see how to do that in a bit) Now of course you want to reward players for having a bank account - how about 100% APR for premium players and 50% for non-premium players. That's still way more than any of us will ever see in real life from a high-street bank, but it's not introducing too much money into the system. Again .. a little bit of math $principal = 100; $annualPercentageRate = 100; $futureValue = $princial * (1 + $annualPercentageRate / 100); Will let you play with the APR figure to see just what sort of cash you will be bringing into your game. To apply this as a multiplier on a daily basis... $daysInYear = 365; $dailyMultiplier = pow($futureValue / $principal, 1 / $daysInYear); So for 100% APR, this yields a daily multiplier of 1.001900838. If you insist on using crons then you could adjust your daily cron to : UPDATE users SET bankmoney = bankmoney * 1.001900838; Caveat, due to rounding, you won't actually see any interest until you have around a $270 in the bank, but after that it will kick in - maybe that's a good thing! This is a lot more gentle on the amount of money coming into the game and is actually quite manageable - at least from personal experience. If you are feeling brave, then as long as you have the date-time the user deposited money into the bank, and the principal amount, you can compute the present value (I use the term present rather than future here but it means pretty much the same thing) at any point to show them what the have now. For reference: $dailyMultiplier = 1.001900838; $prinicpal = 100; $daysSinceDeposited = 50; $presentValue = pow($dailyMultiplier, $daysSinceDeposited) * $principal; With these calculations, I've never need to use a cron to update my bank, and in fact I can actually claw a little money bank from the player by rounding the number of days down to a whole number (which may be have 3.5 days) so they lose out on a little interest but still get the benefit of an apparent 100% APR interest especially if they leave their money alone! The moral of the story here is ... well I'm not sure. It probably goes a long way to explain why so may MCCodes fail - their inflation turns rampant as smart players bank their money early and often. There are some nice solutions I've found such as variable APRs depending on key stock holdings and/or premium status, limiting the amount of cash that can be deposited by combat level (so the higher combat level you are, the more likely you will have large amounts of cash on you to entice players to PvP). You might also look to variable APRs where it reduces to try and keep the overall interest across all players to a certain value, so as more players join, the global APR reduces slightly. What's your take on it? How do you stop players making money? If you are happy to run an MCCode game with the original SQL in the daily cron - let me know a link to your game! I always enjoy breaking the bank!
  14. I've seen some stock markets in MCCodes-based systems, and sadly most fail for a number of reasons. A classic example being one I came across a year or so back. From what I can ascertain from playing it; it had a cron running every 5 or 10 minutes changing the price to a random value with a set range. This meant that any smart player would monitor the market for a few days to determine the lower and upper bounds, then buy when within lowest 10-20% of the price range, and sell in the upper 80-90% of the range. Oddly, I had a mail from the admin/owner asking how I was managing to make so much money so quickly... Despite telling them, they failed to understand the underlying problem, and banned me a day or so later. My fault ? Possibly, I guess it is abusing a bug, but it's a bug that despite explaining it, the owner failed to take steps to address it in a timely manner. There's actually a number of problems here : Random values do not a stock market make. Using crons to update the prices. Failure to log actions on the market. And critically, not play-testing prior to release. So who to we address these ? 1. Use a noise generator There's a number of decent noise generators out there, though I find that Simplex is probably the best for mimicking a stock market. Remember, in a real market, prices are driven by "market forces", which essentially means the amount of stocks that are being bought and sold. As a game simply won't have the number of players to drive this, then using a noise function seems to produce the best result. 2. Crons are unnecessary when you can generate the values via your function in real time. Assume your noise functions is defined as : function noise( float $x ): float then you can pass in a UNIX timestamp scaled by (say) 1/255 (i.e. multiply by 0.003921568627451), then may get a result in the range -0.7888 to 0.6373. Multiple this by (say) 128 and add 127 you would see a range of 26.023 to 208.583. So while the main input into your noise function is essentially a time value you would actually be using it like: $timeScaleFactor = 1 / 255; $rangeFactor = 128; $rangeOffset = 127; $result = noise($time * $timeScaleFactor) * $rangeFactor + $rangeOffset; This gives you plenty of scope to vary the stock prices by changing the input variables, and the best bit is, being deterministic, the $result will always be the same for the same input values - so no need to use crons, you can generate the price at any time on the fly. 3. Logging actions There are multiple options here, probably the simplest is just to store the player-id, the transaction type (buy or sell), the stock-id, the number of stocks, and the price they were bought or sold at. Of course, you may not really need to log transactions assume you follow the next step closely. 4. Play testing This is extremely important, and sadly something that is often missed by owner/admins. Ideally unit and feature tests should be present to ensure the site actually works using either PHPUnit or PestPHP. After that, you can consider acceptance testing or simple just make sure you manually test EVERY page in your project. And that's not just a case of seeing if the page loads - drop in some bad data; i.e. try and buy -1 stocks, or putting string data into numeric fields. There's a number of techniques that people favor here - I tend to rely heavily on feature tests; but they may not pick up everything if you are lazy when writing your tests. Going back to the original example what would these points have addressed? Well for one, using a non-random system makes it more difficult to predict the outcome of the stocks despite being deterministic. Time is one factor, but you still may have to consider limiting the number of stocks available, or perhaps ensure that you cannot sell stocks for maybe 14 days. Without crons, we've got rid of a hidden problem and extra load which simply is no longer needed, though you would need to to do more work to present a stock graph rather than just pulling data out of the db. Logging is of course a simple one to address, and may not be needed if you trust your code and understand any potential pitfalls. Finally testing is probably the biggest one here. Without testing your code, you run the risk of missing something that may seem stable and fits in nicely in your project but suddenly introduces massive amounts of funds making your game highly unstable. If anybody has suggestions in improving the buy/sell experience, let us know - it's a potentially useful module for many games as it can tie up money for long periods of time. Finally, for reference, here's an example but of code you might want to play with: function noise(float $x): float { $permute = [ 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, ]; $i0 = $x > 0 ? (int)$x : (int)$x - 1; $i1 = $i0 + 1; $x0 = $x - $i0; $x1 = $x0 - 1.0; $t0 = 1.0 - $x0 * $x0; $t0 *= $t0; $n0 = $t0 * $t0 * $noiseGradient($permute[$i0 & 0xFF], $x0); $t1 = 1.0 - $x1 * $x1; $t1 *= $t1; $n1 = $t1 * $t1 * $noiseGradient($permute[$i1 & 0xFF], $x1); // The maximum value of this noise is 8 * (3 / 4) ^ 4 = 2.53125 // A factor of 0.395 scales to fit exactly within [-1,1] return 0.395 * ($n0 + $n1); } function noiseGradient(int $hash, float $x): float { $h = $hash & 15; $grad = 1.0 + ($h & 7); if (($h & 8) != 0) { $grad = -$grad; } return $grad * $x; } And just for giggle ... something I was experimenting with -- apologies for the un-formatted dates
  15. Why not use something like omnipay? It supports a huge variety of payment gateways - including Paypal, with all the same interface so you can chop and change to suit. It has the added advantage of being free. (Sorry Dave!)
×
×
  • Create New...