235 lines
13 KiB
Clojure
235 lines
13 KiB
Clojure
(ns auto-ap.ssr.company.plaid
|
|
(:require [auto-ap.datomic
|
|
:refer [add-sorter-fields apply-pagination apply-sort-3 conn merge-query
|
|
pull-attr pull-many-by-id query2]]
|
|
[auto-ap.graphql.utils :refer [assert-can-see-client]]
|
|
[auto-ap.logging :as alog]
|
|
[auto-ap.plaid.core :as p]
|
|
[auto-ap.ssr-routes :as ssr-routes]
|
|
[auto-ap.ssr.components :as com]
|
|
[auto-ap.ssr.grid-page-helper :as helper]
|
|
[auto-ap.ssr.hx :as hx]
|
|
[auto-ap.ssr.svg :as svg]
|
|
[auto-ap.ssr.utils :refer [html-response]]
|
|
[auto-ap.time :as atime]
|
|
[bidi.bidi :as bidi]
|
|
[clj-time.coerce :as coerce]
|
|
[clj-time.core :as time]
|
|
[clojure.string :as str]
|
|
[datomic.api :as dc]
|
|
[hiccup2.core :as hiccup]))
|
|
|
|
(def default-read '[:db/id
|
|
:plaid-item/external-id
|
|
:plaid-item/access-token
|
|
:plaid-item/last-updated
|
|
:plaid-item/status
|
|
|
|
{:plaid-item/accounts [:db/id
|
|
{:bank-account/_plaid-account [{:bank-account/integration-status
|
|
[{ [ :integration-status/state :xform iol-ion.query/ident] [:db/ident]}
|
|
:integration-status/message
|
|
:integration-status/last-attempt
|
|
:integration-status/last-updated]}]}
|
|
:plaid-account/external-id
|
|
:plaid-account/number
|
|
:plaid-account/balance
|
|
:plaid-account/name]}])
|
|
|
|
(defn fetch-ids [db request]
|
|
(let [query-params (:parsed-query-params request)
|
|
query (cond-> {:query {:find []
|
|
:in ['$ '[?xx ...]]
|
|
:where ['[?e :plaid-item/client ?xx]]}
|
|
:args [db (:trimmed-clients request)]}
|
|
|
|
(:sort query-params) (add-sorter-fields {"external-id" ['[?e :plaid-item/external-id ?sort-external-id]]
|
|
"status" ['[?e :plaid-item/status ?sort-status]]}
|
|
query-params)
|
|
|
|
true
|
|
(merge-query {:query {:find ['?e]
|
|
:where ['[?e :plaid-item/external-id]]}}))]
|
|
|
|
(clojure.pprint/pprint query-params)
|
|
(cond->> (query2 query)
|
|
true (apply-sort-3 query-params)
|
|
true (apply-pagination query-params))))
|
|
|
|
|
|
(defn hydrate-results [ids db _]
|
|
(let [results (pull-many-by-id db default-read ids)]
|
|
(->> ids
|
|
(map results))))
|
|
|
|
(defn fetch-page [request]
|
|
(let [db (dc/db conn)
|
|
{ids-to-retrieve :ids matching-count :count} (fetch-ids db request)]
|
|
[(hydrate-results ids-to-retrieve db request)
|
|
matching-count]))
|
|
|
|
|
|
|
|
(defn plaid-link-script [token]
|
|
(format "window.plaid = Plaid.create(
|
|
{ token: \"%s\",
|
|
onSuccess: function (x) { htmx.trigger(\"#link-account\", \"linked\", {\"public_token\": x})}
|
|
})", token))
|
|
|
|
|
|
(defn link [{{client-code "client_code" public-token "public_token"} :form-params
|
|
:keys [identity]
|
|
:as request}]
|
|
(alog/info ::linking
|
|
:request request)
|
|
(when-not client-code
|
|
(throw (ex-info "Client not provided" {:validation-error "Client not provided."})))
|
|
(when-not public-token
|
|
(throw (ex-info "Public token not provided" {:validation-error "public token not provided"})))
|
|
(alog/info ::linking-plaid :id identity :client-code client-code)
|
|
(assert-can-see-client identity (pull-attr (dc/db conn) :db/id [:client/code client-code]))
|
|
(let [access-token (:access_token (p/exchange-public-token public-token client-code))
|
|
account-result (p/get-accounts access-token )
|
|
item {:plaid-item/client [:client/code client-code]
|
|
:plaid-item/external-id (-> account-result :item :item_id )
|
|
:plaid-item/access-token access-token
|
|
:plaid-item/status (or (some-> account-result :item :error)
|
|
"SUCCESS")
|
|
:plaid-item/last-updated (coerce/to-date (time/now))
|
|
:db/id "plaid-item"}]
|
|
|
|
@(dc/transact conn (->> (:accounts account-result)
|
|
(map (fn [a]
|
|
(let [balance (some-> a :balances :current (* 0.01))]
|
|
(cond-> {:plaid-account/external-id (:account_id a)
|
|
:plaid-account/number (:mask a)
|
|
:plaid-account/name (str (:name a) " " (:mask a))
|
|
:plaid-item/_accounts "plaid-item"}
|
|
balance (assoc :plaid-account/balance balance)))))
|
|
(into [item])))
|
|
(alog/info ::access-token-was :token access-token)
|
|
{:headers {"Hx-redirect" (bidi/path-for ssr-routes/only-routes
|
|
:company-plaid)}}))
|
|
|
|
(defn relink [{{:strs [plaid-item-id]} :query-params :keys [identity]}]
|
|
|
|
(let [pi (dc/pull (dc/db conn)
|
|
[:plaid-item/access-token {:plaid-item/client [:client/code]}]
|
|
(Long/parseLong plaid-item-id))]
|
|
(assert-can-see-client identity (pull-attr (dc/db conn) :db/id [:client/code (-> pi :plaid-item/client
|
|
:client/code)]))
|
|
(html-response
|
|
[:div
|
|
[:script (hiccup/raw (plaid-link-script (p/get-relink-token (-> pi :plaid-item/client
|
|
:client/code)
|
|
(-> pi :plaid-item/access-token))))]
|
|
[:script (hiccup/raw "window.plaid.open()")]
|
|
#_(com/button {:color :primary
|
|
:id "link-account"
|
|
:onClick "window.plaid.open()"}
|
|
(com/button-icon {} svg/refresh)
|
|
"Start relink")])))
|
|
|
|
|
|
(def grid-page
|
|
(helper/build
|
|
{:id "plaid-table"
|
|
:nav com/company-aside-nav
|
|
:fetch-page fetch-page
|
|
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
|
|
:company)}
|
|
"My Company"]
|
|
|
|
[:a {:href (bidi/path-for ssr-routes/only-routes
|
|
:company-plaid)}
|
|
"Plaid"]]
|
|
:title "Plaid Accounts"
|
|
:entity-name "Plaid accounts"
|
|
:route :company-plaid-table
|
|
:action-buttons (fn [request]
|
|
(when-let [client-code (:client/code (:client request))]
|
|
[[:div {:hx-post (str (bidi/path-for ssr-routes/only-routes
|
|
:company-plaid-link
|
|
:request-method :post))
|
|
:hx-vals (hiccup/raw (format "js:{client_code: \"%s\", public_token: event.detail.public_token}", client-code))
|
|
:hx-trigger "linked"}
|
|
[:script (hiccup/raw (plaid-link-script (p/get-link-token client-code)))]
|
|
(com/button {:color :primary
|
|
:id "link-account"
|
|
:onClick "window.plaid.open()"}
|
|
(com/button-icon {} svg/refresh)
|
|
(format "Link %s account" client-code))]]))
|
|
:row-buttons (fn [request e]
|
|
[[:div (com/button {:hx-put (str (bidi/path-for ssr-routes/only-routes
|
|
:company-plaid-relink)
|
|
"?plaid-item-id=" (:db/id e))
|
|
:color :primary
|
|
:hx-target "closest div"}
|
|
"Reauthenticate")]])
|
|
:headers [{:key "plaid-item"
|
|
:name "Plaid Item"
|
|
:sort-key "external-id"
|
|
:render :plaid-item/external-id}
|
|
{:key "integreat-plaid-status"
|
|
:name "Integreat ↔ Plaid status"
|
|
:sort-key "integreat-plaid-status"
|
|
:render (fn [e]
|
|
|
|
(let [bad-integration (->> (:plaid-item/accounts e)
|
|
(map (comp
|
|
first
|
|
:bank-account/_plaid-account))
|
|
(filter (comp #{:integration-state/failed :integration-state/unauthorized}
|
|
:integration-status/state
|
|
:bank-account/integration-status))
|
|
first
|
|
:bank-account/integration-status)]
|
|
[:div
|
|
(when bad-integration
|
|
{:x-popper (hx/json {:source "$refs.button"
|
|
:tooltip "$refs.tooltip"})
|
|
:x-data (hx/json {})
|
|
})
|
|
[:div.cursor-pointer (com/pill {:color (if bad-integration
|
|
:red
|
|
:primary) :x-ref "button"}
|
|
|
|
[:div.inline-flex.gap-2
|
|
(or
|
|
(some-> bad-integration
|
|
:integration-status/state
|
|
name
|
|
str/capitalize)
|
|
"Success")
|
|
(when bad-integration
|
|
" (detail)")
|
|
|
|
|
|
(when bad-integration
|
|
(com/tooltip {:x-ref "tooltip"}
|
|
[:div.text-red-700
|
|
(:integration-status/message bad-integration)]))])]
|
|
[:div.grid.grid-cols-2.gap-1.auto-cols-min.grid-flow-row.shrink
|
|
[:div "Attempted: "] [:div (atime/unparse-local (coerce/to-date-time (:integration-status/last-attempt e)) atime/normal-date)]
|
|
[:div "Last Updated: "] [:div (atime/unparse-local (coerce/to-date-time (:integration-status/last-updated e)) atime/normal-date)]]]))}
|
|
{:key "plaid-bank-status"
|
|
:name "Plaid ↔ Bank Status"
|
|
:sort-key "plaid-bank-status"
|
|
:render (fn [e]
|
|
(when-let [status (:plaid-item/status e)]
|
|
[:div [:div (com/pill {:color :primary}
|
|
status)]
|
|
[:div (atime/unparse-local (coerce/to-date-time (:plaid-item/last-updated e)) atime/normal-date)]]))}
|
|
|
|
{:key "accounts"
|
|
:name "Accounts"
|
|
:show-starting "md"
|
|
:render (fn [e]
|
|
[:ul
|
|
(for [a (:plaid-item/accounts e)]
|
|
[:li (:plaid-account/name a) " - " (:plaid-account/number a)])])}]}))
|
|
|
|
|
|
(def page (helper/page-route grid-page))
|
|
(def table (helper/table-route grid-page))
|