RubyConf 2015

Mo Money Mo Problems (with Ruby)

Mo Money Mo Problems (with Ruby)

by Federico Soria

In the talk "Mo Money Mo Problems" at RubyConf 2015, Federico Soria, CTO of Paper Group, shares his extensive experience on best practices for managing money transactions in applications. Soria, a seasoned Ruby developer, highlights the complexities of handling financial data and transactions derived from his work within a payment-focused startup.

The discussion centers around several critical best practices and common pitfalls when dealing with monetary operations:

  • Use of Data Types: Soria emphasizes the importance of using integers rather than floating-point numbers to represent monetary values. Since floats can lead to inaccuracies due to their approximate nature in storage, he advocates for storing values in cents, which eliminates decimal-related problems.

  • Rounding Methods: The talk covers the necessity of rounding in financial calculations. Soria recommends using "round half to even" (bankers' rounding) to ensure fair distribution of rounding outcomes, pointing out that Ruby's built-in methods can facilitate this process through BigDecimal.

  • Avoiding Mix-Ups: Mixing integers with floats can lead to unexpected results, particularly in financial calculations. Soria warns against this and discusses the implications of handling multiple currencies, advising against immediate currency conversion to avoid complications due to fluctuating foreign currency values.

  • Creating a Money Object: To encapsulate monetary values and associated operations, he recommends developers define a custom money object. Using existing libraries, such as the 'money' gem, can simplify handling of various calculations and conversions systematically.

  • PCI Compliance: The talk highlights the significance of understanding PCI compliance when processing payments, stressing that back-end transaction handling is vital to avoid losing money through technical mishaps. Utilizing tools like Active Job or Sidekiq to manage background processing can mitigate transaction errors effectively.

  • Maintaining Transaction Integrity: Soria discusses strategies for ensuring successful transaction processing, such as using Active Record transactions to safely bundle operations and ensuring synchronized timestamps to avoid discrepancies across systems.

  • Importance of a Ledger: He underscores the necessity of maintaining a secure and immutable ledger for recording transactions, which is pivotal for regulatory compliance and protecting against disputes.

  • Adapting to Evolving Payment APIs: Highlighting the need for flexibility in payment processing, he advises employing abstract classes in your code to easily switch payment processors when necessary, thereby addressing the constantly evolving landscape of payment technologies.

  • Fraud Detection: Finally, Soria advises against jumping straight into machine learning for fraud prevention. Instead, he promotes using simple, observable behavioral indicators to assess transaction legitimacy effectively.

In conclusion, Soria’s talk provides valuable advice grounded in practical experience, focusing on ensuring the accuracy, integrity, and security of money transactions while emphasizing the importance of good coding practices in Ruby.

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!