00:00:14.240
Hi everyone, welcome to the talk 'Mo Money Mo Problems' with Ruby. This is not a Tender Love talk; that's somewhere else. My name is Federico, and you can find me on Twitter at @fetaSoria. I'm a Ruby developer from Mexico, and I know some of you are from here as well. I come from Monterrey. I have been working with Ruby for about eight to nine years now, and this is my sixth Ruby talk at a Ruby conference. As they say, I’m a longtime listener and a first-time caller; this is my first talk here.
00:00:38.650
I work at Paper Group in San Francisco, where I am the CTO. The reason I'm sharing this talk is relevant to the startup I’ve been working at for the past three years. Let me give you a little background on what we do. Basically, we are a checkout option. As you go through a shopping cart, you'll see a button that says 'Split the Cost with Payment Group.' You click on it, enter the email addresses, and it splits the bill.
00:01:02.000
We do something similar to a Kickstarter model, so when the entire group comes together, we charge all the cards at that moment. This brings a level of complexity greater than typical transactions; for instance, we may have groups of 200 people, which means we need to execute 200 transactions within milliseconds at the same time. You can imagine the challenges that arise with card failures and other issues, making this a very relevant subject.
00:01:37.189
So, again, the theme is 'More Money More Problems with Ruby'. You might have been expecting something different, but I’m actually going to pivot and discuss the do's and don'ts of handling money. To get started, let’s talk about money and databases. A lot of people, especially developers, may be aware of the complexities here, but there’s a significant issue when attempting this.
00:02:08.590
I remember discussing this with a few developers who tend to blame JavaScript for any errors, but really, the problem isn’t rooted in the language or framework. It stems from how floating-point numbers work. Floats are useful but not for everything, as indicated in their definition: they are an approximate representation of a real number. This means there’s a trade-off between range and precision, emphasizing the 'approximate' aspect.
00:02:53.870
The issue with floats derives from how computers operate. In storage, certain bits indicate whether a number is negative or positive. Additional bits signify the exponent, while the remaining bits define the fraction. Using this notation, storing an exact value like one-third in the database is fundamentally impossible. Computers can approximate it, but this results in inaccuracies, which is why you should avoid using floats for monetary values. Please, do not use them.
00:03:50.000
Instead, use integers. Integers don't store decimals, but they work perfectly with money. You can store everything in cents, which simplifies mathematical calculations by eliminating decimal issues. For instance, it's impossible to have a database entry with a fraction of a cent. This forces you to do things correctly.
00:04:04.000
Payment processors often mandate you to send everything in cents as well. Surprisingly, this isn’t yet a universal rule. This lack of standardization is largely due to the existence of BigDecimal; has anyone used BigDecimal in Ruby? It’s fantastic because, in the background, it treats everything as an integer if the database doesn’t support BigDecimal, storing it as an integer and converting it when needed.
00:05:04.000
However, be wary of performance issues that arise with BigDecimal. If you're just starting out and want to experiment with it, that's fine. But as your app scales, performance could become an issue due to the overhead from using BigDecimal and require significantly more RAM. Stick to integers to avoid these performance pitfalls.
00:06:05.737
Another challenge when dealing with money is rounding. For example, when dealing with amounts that include five cents, how do we decide where that money goes? Everyone knows about rounding up versus rounding down, but how many different rounding methods are there? You might be surprised to learn there are almost infinite ways to round numbers, resulting in a ton of research and documentation.
00:06:59.960
When it comes to handling money, we typically focus on one specific method, which is known as 'round half to even,' or bankers' rounding. The reason bankers' rounding is favored is that it creates a fair distribution of rounding outcomes, leading to an equal amount of rounding in any number of transactions. The way this works is simple: when you round to the nearest even number, sometimes it will go down and other times it will go up.
00:07:33.600
In Ruby, this is easily handled with the built-in method, round half even, which you can pull from BigDecimal. I mention BigDecimal again for educational purposes; however, the code could also be integrated into your applications. Ruby's standard library provides ample facilities to ensure this method of rounding can be used uniformly across your money transactions.
00:08:18.350
Another crucial point is not to mix integers with floats. In Ruby, everything is an object, so you might not run into issues when performing operations that don't involve money. However, when mixed with financial-related operations, these mixing practices can lead to unexpected results. In the currency environment, a common question we hear a lot is related to whether to mix currencies. Allow me to clarify that when charging someone, say in the US, and they want to pay from China, there's a dilemma.
00:09:14.640
You might wonder whether to accept that foreign currency or convert it to US dollars. From my experience, I advise against converting to dollars due to the complexities involved which often lead to losses during transactions. Foreign currency values fluctuate widely, and managing these changes can be troublesome. Big companies like Expedia even have separate websites for different currencies, so take heed—to avoid complexity, save the current exchange rate instead.
00:09:56.070
This practice is crucial for future reference; when your financial metrics come into question months later, accurate data is fundamental since exchange rates can drastically differ over time. Addressing the question of how to organize all this can feel overwhelming, especially for Ruby developers accustomed to object-oriented principles.
00:10:41.850
To simplify things, make money an object. You can define your own money object to encapsulate all relevant methods, ensuring the money value behaves correctly within your application context. A great library to consider is the 'money' gem, which integrates these principles and avoids common pitfalls.
00:11:03.880
It handles a multitude of operations seamlessly, making your life easier. For instance, using currency conversion from Google Converter helps simplify transitions from one currency to another while retaining behaviors associated with monetary transactions.
00:11:40.650
Another critical aspect is being aware of PCI compliance, especially when accepting payments. You may encounter numerous questions related to PCI compliance that can take weeks to navigate. A few still don't realize they don't actually handle transaction data in their backends if they accept money through forms or iframes. Acknowledging this fact can considerably reduce your compliance challenges.
00:12:29.220
Still, once you begin managing payment forms directly, be careful to ensure transactions are processed on the server side. Attempting to execute payment requests on the web could lead to dropped requests, which would result in lost money. To maintain control and avoid losses, utilize background processing with tools like Active Job, Sidekiq, etc.
00:13:04.780
Through this, you can ensure that transactions complete successfully while other tasks continue concurrently. It's critical to avoid race conditions when processing transactions. One of the strategies to mitigate these issues is to use transactions with Active Record or mutex operations.
00:13:50.899
Active Record transactions provide an efficient way to ensure that money is consistently processed correctly. If capturing a payment fails, you don't want a related payout to proceed; likewise, if that payout fails, you should ensure the original capture transaction can revert.
00:14:07.550
In my experience with Paper Group, imagine a scenario where you capture funds from 200 cards for a single payout; ensuring that no payouts occur until all captures have successfully completed is paramount. This diligence minimizes the chances of invoking chargebacks or issues further down the line.
00:14:55.750
Another aspect to remember is the use of mutexes, which can help synchronize various processes, ensuring that only one operation is executed at a time. Whenever multiple captures are involved, leveraging mutex helps maintain the correctness of the total amount captured.
00:15:38.640
As you attempt to orchestrate these processes, it’s vital to remain conscious of your timestamps—ensuring that all systems are using synced time. A scenario where databases might be in different time zones could easily complicate trouble-shooting and support issues in the long run.
00:16:23.359
It bears repeating to maintain a ledger when dealing in financial transactions. Although modern systems can take care of many tasks, the basic act of recording transactions in a secure and immutable way cannot be neglected. This practice not only helps with maintaining regulatory compliance but also safeguards against potential disputes or errors.
00:17:15.180
As developers, we must remain vigilant to maintain this ledger effectively without subjecting users’ data to changes. A strict read-only structure will help ensure that all changes are properly accounted for.
00:17:56.740
This structure prevents the potential for users to manipulate their transaction history, providing everyone with accurate records and data integrity. In scenarios where a user decides to withdraw their consent to your service, ensuring any sensitive financial information is handled securely becomes paramount.
00:18:38.930
This might involve deleting payment methods or gateway information accordingly, recognizing that you might have multiple payment processors integrated into your platform.
00:19:24.480
Deploying abstract classes can enhance your system's modularity, making it easier to swap out payment providers for better rates. As the landscape of payment processing continually evolves, being prepared for changes in your API integration keeps flexibility at the core of your payment system.
00:20:13.100
Additionally, always remain aware of fraud detection systems. While there's significant hype about machine learning, it shouldn’t be your starting point. Begin by evaluating simple indicators on users' profiles or transaction behaviors to determine authenticity.
00:20:53.540
Check the issuer’s BIN numbers for insights into transaction legitimacy. This process provides essential markers that indicate whether transactions align with typical behavior.
00:21:38.620
Remember, while diving deep into machine learning is valuable, progressive risk assessment through simpler tactics is key. Utilizing a range of mitigations gleaned from basic behavioral observations fosters a robust fraud prevention mechanism. Thank you for your attention, and please feel free to ask any questions you might have!
00:24:03.480
And by the way, we are hiring! If you are interested in joining us, please feel free to reach out after this talk!