DEV Community

Cover image for Having fun with money
Daniel Fitzpatrick
Daniel Fitzpatrick

Posted on

Having fun with money

I reached a significant financial milestone this month and am focusing on new goals. Like solving a performance problem, you should always measure first.

After poking around at different account aggregation tools, I settled on pocketsmith. Their simple REST API was a main draw:

That task alone wasn't interesting enough, so I wrote a small Clojure library to talk to the API. Ideally, it would leverage something like martian, but my attempt was unsuccessful. That could be a task for the future.

GitHub logo crinklywrappr / pocketsmith-api

small wrapper around the pocketsmith api for clojure developers

com.github.crinklywrappr/pocketsmith-api

Clojure library for interacting with the Pocketsmith REST API. Small, opinionated, and hand-crafted.

Coordinates

com.github.crinklywrappr/pocketsmith-api {:mvn/version "1.0.37"}
Enter fullscreen mode Exit fullscreen mode

Usage

First, require the library and define your user key. You can generate a key on the dashboard from your pocketsmith account.

(require '[crinklywrappr.pocketsmith-api.core :as ps])
(def mykey "xxx")
Enter fullscreen mode Exit fullscreen mode

Then, begin querying.

(def myuser (ps/authorized-user mykey :convert? true :minify? true))
;=> {:name "John Doe",
     :login "my-username"
     :email "my-address@email.com",
     :week-start-day 0,
     :id 1111111,
     :base-currency-code #object[org.joda.money.CurrencyUnit 0x4d1b5405 "USD"],
     :time-zone #object[org.joda.time.tz.CachedDateTimeZone 0x468ce331 "America/Chicago"]}

(def mycategories (into [] (ps/categories mykey myuser :flatten? true :normalize? true :convert? true :minify? true)))
;=> [{:id XXX, :title "Charity", :parents []}
     {:id XXX, :title "Taxes", :parents []}
     {:id XXX, :title "Bank", 
Enter fullscreen mode Exit fullscreen mode

pocketsmith-api uses Joda Time & Joda Money and has 99% code coverage. Because it deals with money, I wanted to ensure it read amounts accurately in various currencies.

That said, Joda Money has some limitations. I have listed some on the README.

Let's build something with it!

Acorns...?

Acorns is a savings tool that, in the past, worked by sending users monthly reports of how much they could save if they rounded up all their charges to the nearest dollar and transferred the difference to a savings account.

They may do more now.

I always thought that idea was excellent but I wanted to avoid paying for it. With Pocketsmith, we can accomplish that easily.

Here is the code.

(ns acorns
(:require [clojure.core.reducers :as r]
[crinklywrappr.pocketsmith-api.core :as ps]
[clojurewerkz.money.amounts :as ma]))
(def mykey "XXX")
(defn user []
(ps/authorized-user mykey :convert? true :minify? true))
(defn card? [account]
(= (:type account) "credits"))
(defn cards [user]
(r/filter card? (ps/accounts mykey user :convert? true :minify? true)))
(defn last-month-query [user]
(ps/transaction-query-params
(assoc (ps/last-month user) :type :debit)))
(defn last-month-charges* [user card]
(->>
(ps/account-transactions
mykey user card (last-month-query user)
:normalize? true :convert? true :minify? true)
(into [])))
(defn last-month-charges [user cards]
(->> cards
(r/map (partial last-month-charges* user))
(into []) flatten))
(defn round-up [charge]
(ma/round (:amount charge) 0 :up))
(defn difference [charge]
(ma/minus (:amount charge) (round-up charge)))
(defn acorns []
(let [me (user)]
(->> me cards
(last-month-charges me)
(map difference)
(reduce ma/plus))))
view raw acorns.clj hosted with ❤ by GitHub

Call (acorns) to get the desired amount.

Project setup

I prefer deps-new these days.

clojure -Tnew lib :name <project-name>
Enter fullscreen mode Exit fullscreen mode

Add crinklywrappr/pocketsmith-api to your deps.edn. Clojure will pull in all of the other dependencies you need transitively.

com.github.crinklywrappr/pocketsmith-api {:mvn/version "1.0.37"}
Enter fullscreen mode Exit fullscreen mode

Sanity checks

I like to be extra careful when dealing with money. Please verify some elementary assertions before borrowing this code for any purpose. I will demonstrate some tests on my account and list additional applicable checks.

The Clojure repl is unmatched for this.

Am I looking at the correct accounts? ✓

I only want to consider credit card accounts.

(into [] (r/map :name (cards (user))))
;=>
["Amex card" "Food card" "Amazon card" "Gas card" "Points card"]
Enter fullscreen mode Exit fullscreen mode

Am I requesting the correct transactions? ✓

Specifically, we only want to look at last month's debit transactions. The current date is June 19, 2023.

(last-month-query (user))
;=>
{:start_date "2023-05-01", :end_date "2023-05-31", :type "debit", :per_page 100}
Enter fullscreen mode Exit fullscreen mode

Am I receiving the correct transactions? ✓

Easy. We can sort the transactions by date and inspect the first and last elements.

(let [me (user)]
  (->> (last-month-charges me (cards me))
       (sort-by :date clj-time.core/before?)
       ((juxt first last))
       (mapv #(select-keys % [:date :payee]))))
;=>
[{:date
  #object[org.joda.time.LocalDateTime 0x5df45749 "2023-05-01T00:00:00.000"],
  :payee "SPOTIFY NEW YORK NY P22C7B5A33 XXXXXX1161"}
 {:date
  #object[org.joda.time.LocalDateTime 0x140ae942 "2023-05-31T00:00:00.000"],
  :payee "DOORDASH*LUNCH BOX CSAN FRANCIS NT_NZXE7CMB +XXXXXXX9470"}]
Enter fullscreen mode Exit fullscreen mode

Does the math make sense? ✓

We can do this by taking 10 of the transactions and eyeballing the numbers.

(require '[clojurewerkz.money.format :as mf])
(let [me (user)]
  (clojure.pprint/print-table
   (->> (last-month-charges me (cards me))
        (map
         (fn [x]
           {:rounded (mf/format (round-up x))
            :amount (mf/format (:amount x))
            :difference (mf/format (difference x))}))
        (take 10))))

| :rounded | :amount | :difference |
|----------+---------+-------------|
|  $-55.00 | $-54.80 |       $0.20 |
|  $-26.00 | $-25.44 |       $0.56 |
|  $-29.00 | $-28.02 |       $0.98 |
|  $-23.00 | $-22.19 |       $0.81 |
|   $-3.00 |  $-2.99 |       $0.01 |
|  $-38.00 | $-37.94 |       $0.06 |
|  $-50.00 | $-49.81 |       $0.19 |
|  $-16.00 | $-15.44 |       $0.56 |
|  $-24.00 | $-23.90 |       $0.10 |
|   $-1.00 |  $-0.64 |       $0.36 |

Enter fullscreen mode Exit fullscreen mode

$54.80 rounds up to $55.00, and the difference is $0.20, and so forth.

Additional checks

There are other assertions you may want to test, and I encourage you to do so.

  • Are all the differences under $1.00?
  • Does difference respond with $0.00 when given an even dollar charge?
  • Is the total amount less than the number of charges?
  • Are all the charges truly debits (negative dollar amounts)?

Conclusion

This project was a fun distraction while gearing up for my next financial goals. Hopefully, this selfish exercise will help someone else. 😉

By the way, I need to squirrel away $40.63 from last month.

Speedy emails, satisfied customers

Postmark Image

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay