diff --git a/config/dev.edn b/config/dev.edn index c9a7e936..08c40b31 100644 --- a/config/dev.edn +++ b/config/dev.edn @@ -14,4 +14,6 @@ :yodlee-user-login "sbMemda48aa19712a83c3ca4e935dd5e5d46b1a1" :yodlee-user-password "sbMemda48aa19712a83c3ca4e935dd5e5d46b1a1#123" :yodlee-base-url "https://developer.api.yodlee.com/ysl" + :yodlee-app "10003600" + :yodlee-fastlink "https://node.developer.yodlee.com/authenticate/restserver/?channelAppName=restserver" } diff --git a/config/local.edn b/config/local.edn index 4a06d221..aa9baba1 100644 --- a/config/local.edn +++ b/config/local.edn @@ -13,4 +13,6 @@ :yodlee-user-login "sbMemda48aa19712a83c3ca4e935dd5e5d46b1a1" :yodlee-user-password "sbMemda48aa19712a83c3ca4e935dd5e5d46b1a1#123" :yodlee-base-url "https://developer.api.yodlee.com/ysl" + :yodlee-app "10003600" + :yodlee-fastlink "https://node.developer.yodlee.com/authenticate/restserver/?channelAppName=restserver" } diff --git a/config/prod.edn b/config/prod.edn index cfd5f221..b5a863c1 100644 --- a/config/prod.edn +++ b/config/prod.edn @@ -14,4 +14,6 @@ :yodlee-user-login "integreat" :yodlee-user-password "Import3transactions!" :yodlee-base-url "https://quickstart2.api.yodlee.com/ysl" + :yodlee-app "10003600" + :yodlee-fastlink "https://quickstartus2node.yodleeinteractive.com/authenticate/qstartus12/?channelAppName=quickstartus2" } diff --git a/config/staging.edn b/config/staging.edn index fe0c898e..4d19dafe 100644 --- a/config/staging.edn +++ b/config/staging.edn @@ -14,4 +14,6 @@ :yodlee-user-login "integreat" :yodlee-user-password "Import3transactions!" :yodlee-base-url "https://quickstart2.api.yodlee.com/ysl" + :yodlee-app "10003600" + :yodlee-fastlink "https://quickstartus2node.yodleeinteractive.com/authenticate/qstartus12/?channelAppName=quickstartus2" } diff --git a/src/clj/auto_ap/handler.clj b/src/clj/auto_ap/handler.clj index 6ed928c5..ac494468 100644 --- a/src/clj/auto_ap/handler.clj +++ b/src/clj/auto_ap/handler.clj @@ -6,6 +6,7 @@ [auto-ap.routes.reminders :as reminders] [auto-ap.routes.graphql :as graphql] [auto-ap.routes.vendors :as vendors] + [auto-ap.routes.yodlee :as yodlee] [auto-ap.routes.events :as events] [auto-ap.routes.checks :as checks] [auto-ap.routes.exports :as exports] @@ -33,6 +34,7 @@ (defroutes api-routes (context "/api" [] exports/routes + yodlee/routes invoices/routes companies/routes vendors/routes diff --git a/src/clj/auto_ap/routes/events.clj b/src/clj/auto_ap/routes/events.clj index 64c7e2c1..69376127 100644 --- a/src/clj/auto_ap/routes/events.clj +++ b/src/clj/auto_ap/routes/events.clj @@ -17,6 +17,7 @@ (defroutes routes (context "/events" [] + (POST "/yodlee-import" {:keys [query-params headers body] :as x} (let [notification-type (get headers "x-amz-sns-message-type")] (println "Received notification " notification-type) diff --git a/src/clj/auto_ap/routes/yodlee.clj b/src/clj/auto_ap/routes/yodlee.clj new file mode 100644 index 00000000..56d34e88 --- /dev/null +++ b/src/clj/auto_ap/routes/yodlee.clj @@ -0,0 +1,40 @@ +(ns auto-ap.routes.yodlee + (:require + [auto-ap.graphql :as graphql] + [clj-http.client :as http] + + [auto-ap.yodlee.core :as yodlee] + [auto-ap.graphql.utils :refer [->graphql assert-admin]] + [auto-ap.routes.utils :refer [wrap-secure]] + [clj-time.coerce :refer [to-date]] + [auto-ap.db.invoices-expense-accounts :as expense-accounts] + [ring.middleware.json :refer [wrap-json-response]] + [compojure.core :refer [GET POST context defroutes wrap-routes]] + [clojure.string :as str] + [config.core :refer [env]] + )) + +(defroutes routes + (wrap-routes + (context "/yodlee" [] + (GET "/fastlink" {:keys [query-params identity] :as request} + (assert-admin identity) + (let [[session token] (yodlee/get-access-token)] + + + {:status 200 + :headers {"Content-Type" "application/edn"} + :body (pr-str {:session session + :token token + :app (:yodlee-app env) + + :url (:yodlee-fastlink env) + + }) })) + (GET "/accounts" {:keys [query-params identity] :as request} + (assert-admin identity) + (let [[session token] (yodlee/get-access-token)] + {:status 200 + :headers {"Content-Type" "application/edn"} + :body (pr-str (yodlee/get-accounts)) }))) + wrap-secure)) diff --git a/src/clj/auto_ap/yodlee/core.clj b/src/clj/auto_ap/yodlee/core.clj index c5500708..636f78ca 100644 --- a/src/clj/auto_ap/yodlee/core.clj +++ b/src/clj/auto_ap/yodlee/core.clj @@ -43,7 +43,9 @@ user-session (login-user cob-session)] (-> (str (:yodlee-base-url env) "/accounts") (client/get {:headers (merge base-headers {"Authorization" (auth-header cob-session user-session)}) - :as :json})))) + :as :json}) + :body + :account))) (defn get-provider-accounts [] (let [cob-session (login-cobrand) @@ -75,6 +77,23 @@ (recur (concat transactions transaction-batch) (+ batch-size skip)) transactions))))) +(defn get-access-token [] + (let [cob-session (login-cobrand) + user-session (login-user cob-session) + token (-> + (str (:yodlee-base-url env) "/user/accessTokens?appIds=" 10003600) + + (client/get + {:headers (merge base-headers {"Authorization" (auth-header cob-session user-session)}) + :as :json}) + :body + :user + :accessTokens + first + :value + )] + [user-session token])) + (defn create-user [] (let [cob-session (login-cobrand)] (-> (str (:yodlee-base-url env) "/user/register") diff --git a/src/clj/auto_ap/yodlee/import.clj b/src/clj/auto_ap/yodlee/import.clj index 0c49ad76..0350ab53 100644 --- a/src/clj/auto_ap/yodlee/import.clj +++ b/src/clj/auto_ap/yodlee/import.clj @@ -68,23 +68,24 @@ (try - (transactions/upsert! - {:post-date (time/parse post-date "YYYY-MM-dd") - :id (sha-256 (str id)) - :account-id account-id - :date (time/parse date "YYYY-MM-dd") - :amount amount - :description-original description-original - :description-simple description-simple - :type type - :status status - :company-id company-id - :check-number check-number - :bank-account-id (transaction->bank-account-id transaction) - :check-id check-id - }) - (when check-id - (checks/update! {:id check-id :status "cleared"})) + (when company-id + (transactions/upsert! + {:post-date (time/parse post-date "YYYY-MM-dd") + :id (sha-256 (str id)) + :account-id account-id + :date (time/parse date "YYYY-MM-dd") + :amount amount + :description-original description-original + :description-simple description-simple + :type type + :status status + :company-id company-id + :check-number check-number + :bank-account-id (transaction->bank-account-id transaction) + :check-id check-id + }) + (when check-id + (checks/update! {:id check-id :status "cleared"}))) (catch Exception e (println e))))) diff --git a/src/cljs/auto_ap/routes.cljs b/src/cljs/auto_ap/routes.cljs index ee7d1ae7..041f828d 100644 --- a/src/cljs/auto_ap/routes.cljs +++ b/src/cljs/auto_ap/routes.cljs @@ -10,7 +10,8 @@ "users" :admin-users "reminders" :admin-reminders "vendors" :admin-vendors - "excel-import" :admin-excel-import} + "excel-import" :admin-excel-import + "yodlee" :admin-yodlee} "invoices/" {"" :invoices "import" :import-invoices "unpaid" :unpaid-invoices diff --git a/src/cljs/auto_ap/views/main.cljs b/src/cljs/auto_ap/views/main.cljs index 818c26d0..be93dcc8 100644 --- a/src/cljs/auto_ap/views/main.cljs +++ b/src/cljs/auto_ap/views/main.cljs @@ -27,6 +27,7 @@ :admin-users :admin-left-panel :admin-excel-import :admin-left-panel :admin-vendors :admin-left-panel + :admin-yodlee :admin-left-panel :admin-reminders :admin-left-panel :new-invoice :blank} page :blank)) @@ -96,6 +97,13 @@ [:span {:class "icon"} [:i {:class "fa fa-envelope-o"}]] [:span {:class "name"} "Users"]]] + + + [:li.menu-item + [:a {:href (bidi/path-for routes/routes :admin-yodlee), :class (str "item" (active-when= ap :admin-yodlee))} + [:span {:class "icon"} + [:i {:class "fa fa-envelope-o"}]] + [:span {:class "name"} "Yodlee Link"]]] [:ul ]] [:p.menu-label "History"] diff --git a/src/cljs/auto_ap/views/pages.cljs b/src/cljs/auto_ap/views/pages.cljs index 8761eaee..d54d8ac2 100644 --- a/src/cljs/auto_ap/views/pages.cljs +++ b/src/cljs/auto_ap/views/pages.cljs @@ -10,6 +10,7 @@ [auto-ap.views.pages.needs-activation :refer [needs-activation-page]] [auto-ap.views.pages.check :refer [check-page]] [auto-ap.views.pages.admin.companies :refer [admin-companies-page]] + [auto-ap.views.pages.admin.yodlee :refer [admin-yodlee-page]] [auto-ap.views.pages.admin.users :refer [admin-users-page]] [auto-ap.views.pages.admin.vendors :refer [admin-vendors-page]] [auto-ap.views.pages.admin.reminders :refer [admin-reminders-page]] @@ -54,6 +55,9 @@ (defmethod active-page :admin-reminders [] [admin-reminders-page]) +(defmethod active-page :admin-yodlee [] + [admin-yodlee-page]) + (defmethod active-page :admin-excel-import [] [admin-excel-import-page]) diff --git a/src/cljs/auto_ap/views/pages/admin/yodlee.cljs b/src/cljs/auto_ap/views/pages/admin/yodlee.cljs new file mode 100644 index 00000000..5cc5e558 --- /dev/null +++ b/src/cljs/auto_ap/views/pages/admin/yodlee.cljs @@ -0,0 +1,129 @@ +(ns auto-ap.views.pages.admin.yodlee + (:require-macros [cljs.core.async.macros :refer [go]]) + (:require [re-frame.core :as re-frame] + [reagent.core :as reagent] + [auto-ap.subs :as subs] + [auto-ap.events.admin.companies :as events] + [auto-ap.entities.companies :as entity] + [auto-ap.views.components.address :refer [address-field]] + [auto-ap.views.utils :refer [login-url dispatch-event dispatch-value-change bind-field horizontal-field]] + [auto-ap.views.components.modal :refer [action-modal]] + [cljs.reader :as edn] + [auto-ap.routes :as routes] + [bidi.bidi :as bidi])) + + + +(re-frame/reg-sub + ::authentication + (fn [db] + (-> db ::yodlee :authentication))) + +(re-frame/reg-sub + ::loading? + (fn [db] + (-> db ::yodlee :loading?))) + +(re-frame/reg-sub + ::accounts + (fn [db] + (-> db ::yodlee :accounts))) + +(re-frame/reg-sub + ::accounts-loading? + (fn [db] + (-> db ::yodlee :accounts-loading?))) + +(re-frame/reg-event-fx + ::authenticate-with-yodlee + (fn [{:keys [db]} _] + {:db (assoc-in db [::yodlee :loading?] true) + :http {:token (:user db) + :method :get + :headers {"Content-Type" "application/edn"} + :uri (str "/api/yodlee/fastlink") + :on-success [::authenticated] + :on-error [::save-error]}})) + +(re-frame/reg-event-fx + ::mounted + (fn [{:keys [db]} _] + {:db (assoc-in db [::yodlee] {:accounts-loading? true}) + :http {:token (:user db) + :method :get + :headers {"Content-Type" "application/edn"} + :uri (str "/api/yodlee/accounts") + :on-success [::got-accounts] + :on-error [::save-error]}})) + +(re-frame/reg-event-fx + ::got-accounts + (fn [{:keys [db]} [_ accounts]] + {:db (-> db + (assoc-in [::yodlee :accounts] accounts) + (assoc-in [::yodlee :accounts-loading?] false))})) + +(re-frame/reg-event-fx + ::authenticated + (fn [{:keys [db]} [_ authentication]] + {:db (-> db + (assoc-in [::yodlee :authentication] authentication) + (assoc-in [::yodlee :loading?] false))})) + + + +(defn yodlee-link-button [] + [:div + (let [authentication @(re-frame/subscribe [::authentication]) + loading? @(re-frame/subscribe [::loading?])] + + (if authentication + [:div + "Authentication successful!" + [:form {:action (:url authentication) :method "POST"} + [:input {:type "hidden" + :name "rsession" + :value (:session authentication)}] + [:input {:type "hidden" + :name "token" + :value (:token authentication)}] + [:input {:type "hidden" + :name "app" + :value (:app authentication)}] + + [:input {:type "hidden" + :name "redirectReq" + :value "true"}] + [:button.button.is-primary [:span [:span.icon [:i.fa.fa-external-link]] " Go to yodlee"]]]] + + [:button.button.is-primary {:class (if loading? "is-loading" "") :on-click (dispatch-event [::authenticate-with-yodlee])} "Authenticate with Yodlee"]))]) + +(defn yodlee-accounts-table [] + + [:div + [:table.table + [:thead + [:tr + [:th "Account Name"] + [:th "Account Number"] + [:th "Yodlee Account Number"]]] + (if @(re-frame/subscribe [::accounts-loading?]) + [:tr [:td {:col-span "3"} "Loading..."] + ] + (for [account @(re-frame/subscribe [::accounts])] + [:tr + [:td (:accountName account)] + [:td (:accountNumber account)] + [:td (:id account)]]))]]) + + +(defn admin-yodlee-page [] + [(with-meta + (fn [] + [:div + [:h1.title "Yodlee"] + + [yodlee-accounts-table] + [yodlee-link-button]]) + {:component-did-mount (fn [] + (re-frame/dispatch [::mounted]))})])