Makes plaid user configurable
This commit is contained in:
@@ -134,7 +134,7 @@
|
||||
:title "1099 Vendors"
|
||||
:entity-name "Vendors"
|
||||
:route :company-1099-vendor-table
|
||||
:action-buttons (fn [user]
|
||||
:action-buttons (fn [user _]
|
||||
nil)
|
||||
:row-buttons (fn [user e]
|
||||
[(com/icon-button {:hx-get (str (bidi/path-for ssr-routes/only-routes
|
||||
|
||||
176
src/clj/auto_ap/ssr/company/plaid.clj
Normal file
176
src/clj/auto_ap/ssr/company/plaid.clj
Normal file
@@ -0,0 +1,176 @@
|
||||
(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 limited-clients]]
|
||||
[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.svg :as svg]
|
||||
[auto-ap.time :as atime]
|
||||
[bidi.bidi :as bidi]
|
||||
[clj-time.coerce :as coerce]
|
||||
[clj-time.core :as time]
|
||||
[datomic.api :as dc]
|
||||
[hiccup2.core :as hiccup]))
|
||||
|
||||
(def default-read '[:db/id
|
||||
:plaid-item/external-id
|
||||
:plaid-item/last-updated
|
||||
:plaid-item/status
|
||||
{:plaid-item/accounts [:db/id
|
||||
:plaid-account/external-id
|
||||
:plaid-account/number
|
||||
:plaid-account/balance
|
||||
:plaid-account/name]}])
|
||||
|
||||
(defn raw-graphql-ids [db args]
|
||||
(let [query (cond-> {:query {:find []
|
||||
:in ['$]
|
||||
:where []}
|
||||
:args [db]}
|
||||
|
||||
(:sort args) (add-sorter-fields {"external-id" ['[?e :plaid-item/external-id ?sort-external-id]]
|
||||
"status" ['[?e :plaid-item/status ?sort-status]]}
|
||||
args)
|
||||
|
||||
(limited-clients (:id args))
|
||||
(merge-query {:query {:in ['[?xx ...]]
|
||||
:where ['[?e :plaid-item/client ?xx]]}
|
||||
:args [ (set (map :db/id (limited-clients (:id args))))]})
|
||||
|
||||
(:client-id args)
|
||||
(merge-query {:query {:in '[?client-id]
|
||||
:where ['[?e :plaid-item/client ?client-id]]}
|
||||
:args [(:client-id args)]})
|
||||
|
||||
true
|
||||
(merge-query {:query {:find ['?e]
|
||||
:where ['[?e :plaid-item/external-id]]}}))]
|
||||
|
||||
(cond->> (query2 query)
|
||||
true (apply-sort-3 args)
|
||||
true (apply-pagination args))))
|
||||
|
||||
|
||||
(defn graphql-results [ids db _]
|
||||
(let [results (pull-many-by-id db default-read ids)]
|
||||
(->> ids
|
||||
(map results))))
|
||||
|
||||
(defn get-page [args]
|
||||
(let [db (dc/db conn)
|
||||
{ids-to-retrieve :ids matching-count :count} (raw-graphql-ids db args)]
|
||||
[(graphql-results ids-to-retrieve db args)
|
||||
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)}}))
|
||||
|
||||
|
||||
(def grid-page {:id "plaid-table"
|
||||
:nav (com/company-aside-nav)
|
||||
:id-fn :db/id
|
||||
:fetch-page (fn [user args]
|
||||
(get-page (assoc args :id user)))
|
||||
: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 [user params]
|
||||
(when-let [client-code (:client/code (:client params))]
|
||||
[[: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)
|
||||
"Link new account")]]))
|
||||
:row-buttons (fn [user e]
|
||||
[]
|
||||
#_[(when (is-admin? user)
|
||||
(com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes
|
||||
:company-yodlee-provider-account-refresh)
|
||||
:hx-target "closest tr"}
|
||||
svg/refresh))])
|
||||
:headers [{:key "plaid-item"
|
||||
:name "Plaid Item"
|
||||
:sort-key "id"
|
||||
:render :db/id}
|
||||
{:key "status"
|
||||
:name "Status"
|
||||
:sort-key "status"
|
||||
:render #(when-let [status (:plaid-item/status %)]
|
||||
[:div [:div (com/pill {:color :primary }
|
||||
status)]
|
||||
[:div (atime/unparse-local (coerce/to-date-time (:plaid-item/last-updated %)) 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 (partial helper/page grid-page))
|
||||
(def table (partial helper/table grid-page))
|
||||
@@ -29,7 +29,7 @@
|
||||
:title "Reports"
|
||||
:entity-name "Reports"
|
||||
:route :company-reports-table
|
||||
:action-buttons (fn [user]
|
||||
:action-buttons (fn [user _]
|
||||
nil)
|
||||
:row-buttons (fn [user e]
|
||||
(com/a-icon-button {:href (:report/url e)}
|
||||
|
||||
@@ -62,7 +62,7 @@ fastlink.open({fastLinkURL: '%s',
|
||||
:title "Yodlee Accounts"
|
||||
:entity-name "Yodlee accounts"
|
||||
:route :company-yodlee-table
|
||||
:action-buttons (fn [user]
|
||||
:action-buttons (fn [user _]
|
||||
[(com/button {:color :primary
|
||||
:on-click "openFastlink()"
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
|
||||
@@ -249,6 +249,11 @@
|
||||
:href (bidi/path-for ssr-routes/only-routes
|
||||
:company-reports)}
|
||||
"Reports")]
|
||||
[:li
|
||||
(menu-button- {:icon svg/bank
|
||||
:href (bidi/path-for ssr-routes/only-routes
|
||||
:company-plaid)}
|
||||
"Plaid Link")]
|
||||
[:li
|
||||
(menu-button- {:icon svg/bank
|
||||
:href (bidi/path-for ssr-routes/only-routes
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
[auto-ap.ssr.transaction.insights :as insights]
|
||||
[auto-ap.ssr.company.company-1099 :as company-1099]
|
||||
[auto-ap.ssr.company.yodlee :as company-yodlee]
|
||||
[auto-ap.ssr.company.plaid :as company-plaid]
|
||||
[auto-ap.ssr.search :as search]
|
||||
[auto-ap.ssr.company-dropdown :as company-dropdown]
|
||||
[auto-ap.ssr.company.reports :as company-reports]
|
||||
@@ -28,6 +29,9 @@
|
||||
:company-1099-vendor-table (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-table))
|
||||
:company-1099-vendor-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-dialog))
|
||||
:company-1099-vendor-save (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-save))
|
||||
:company-plaid (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/page))
|
||||
:company-plaid-table (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/table))
|
||||
:company-plaid-link (wrap-client-redirect-unauthenticated (wrap-secure company-plaid/link))
|
||||
:company-yodlee (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/page))
|
||||
:company-yodlee-table (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/table))
|
||||
:company-yodlee-fastlink-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/fastlink-dialog))
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
))
|
||||
"default sort"))
|
||||
|
||||
(defn table* [grid-spec user {:keys [start per-page client flash-id sort request]}]
|
||||
(defn table* [grid-spec user {:keys [start per-page client flash-id sort request] :as params}]
|
||||
(let [start (or start 0)
|
||||
per-page (or per-page 30)
|
||||
[entities total] ((:fetch-page grid-spec)
|
||||
@@ -70,7 +70,7 @@
|
||||
:subtitle [:div.flex.items-center.gap-2
|
||||
[:span (format "Total %s: %d, " (:entity-name grid-spec) total)]
|
||||
(sort-by-list sort)]
|
||||
:action-buttons ((:action-buttons grid-spec) user)
|
||||
:action-buttons ((:action-buttons grid-spec) user params)
|
||||
:rows (for [entity entities]
|
||||
(row* grid-spec user entity {:flash? (= flash-id ((:id-fn grid-spec) entity))}))
|
||||
:thead-params {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
[:script {:src "https://unpkg.com/hyperscript.org@0.9.7/dist/_hyperscript.min.js"}]
|
||||
[:script {:src "https://unpkg.com/@popperjs/core@2.11.8/dist/umd/popper.min.js"}]
|
||||
[:script {:src "https://cdn.plaid.com/link/v2/stable/link-initialize.js"}]
|
||||
#_[:script {:src "https://unpkg.com/htmx.org@1.8.4"
|
||||
:integrity "sha384-wg5Y/JwF7VxGk4zLsJEcAojRtlVp1FKKdGy1qN+OMtdq72WRvX/EdRdqg/LOhYeV"
|
||||
:crossorigin= "anonymous"}]
|
||||
|
||||
Reference in New Issue
Block a user