Skip to content

Commit

Permalink
Pinwheel merge resolve (#64)
Browse files Browse the repository at this point in the history
* Rename pinwheel, add modal, add CSP, create new service and connect

* Working employer select

* Only include usable service methods

* Return the Pinwheel class

* Remove unused head tag items

* Incl onEvent

* Autocorrect

* Load pinwheel earlier; remove sandbox

* Drop Argyle from CSP directives

* Add tests

* Inital setup

* WIP

* Failing test

* True negative

* Use a better test

* Passing tests

* Paystubs channel

* Subscribing and listneing for webhook events

* add url identifier

* Store link id information

* Lint

* Cleanup

* Rename

* Remove the account creation line

* Rename the channel to be more generic

* Style

* Remove stale

* Store API key as instance var

* Remove stale comment

* Query for accounts/then payroll

* Rubocopify

* Use a UUID for the Pinwheel end_user_id

* Remove pgcrypto extension

It shouldn't be necessary just for the pgcrypto extension.

* Remove almost all mentions of Argyle

This removes all the previous Argyle functionality: controllers, routes,
tests, and fixtures.

This commit leaves in place:
* Database columns (we can handle them later)
* Environmet variables (I will handle that in a second commit now)

* Remove ARGYLE environment variables

This removes the configuration for ARGYLE_SANDBOX,
ARGYLE_WEBHOOK_SECRET, and ARGYLE_API_TOKEN, as we won't need them with
Pinwheel.

* Clear up merge conflict

---------

Co-authored-by: Tom Dooner <[email protected]>
  • Loading branch information
allthesignals and tdooner authored Jun 17, 2024
1 parent 98a3859 commit c83417a
Show file tree
Hide file tree
Showing 38 changed files with 438 additions and 485 deletions.
2 changes: 1 addition & 1 deletion Brewfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ brew "jq"
# queue for sidekiq jobs
brew "redis"

# ngrok local tunnel to receive argyle webhooks
# ngrok local tunnel to receive webhooks
cask "ngrok"

# Terraform version manager for infrastructure
Expand Down
4 changes: 1 addition & 3 deletions app/.env
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
ARGYLE_API_TOKEN=
ARGYLE_SANDBOX=true
ARGYLE_WEBHOOK_SECRET=
NGROK_URL=
CBV_INVITE_SECRET=development
DOMAIN_NAME=localhost
PINWHEEL_API_TOKEN=API secret
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class ArgylePaystubsChannel < ApplicationCable::Channel
class PaystubsChannel < ApplicationCable::Channel
def subscribed
cbv_flow = CbvFlow.find(connection.session[:cbv_flow_id])
stream_for cbv_flow
Expand Down
18 changes: 0 additions & 18 deletions app/app/controllers/api/argyle_controller.rb

This file was deleted.

24 changes: 24 additions & 0 deletions app/app/controllers/api/pinwheel_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class Api::PinwheelController < ApplicationController
# run the token here with the included employer/payroll provider id
def create_token
cbv_flow = CbvFlow.find(session[:cbv_flow_id])
token_response = provider.create_link_token(
response_type: token_params[:response_type],
id: token_params[:id],
end_user_id: cbv_flow.pinwheel_end_user_id
)
token = token_response["data"]["token"]

render json: { status: :ok, token: token }
end

private

def provider
PinwheelService.new
end

def token_params
params.require(:pinwheel).permit(:response_type, :id)
end
end
39 changes: 15 additions & 24 deletions app/app/controllers/cbv_flows_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ def entry
end

def employer_search
@argyle_user_token = fetch_and_store_argyle_token
@query = search_params[:query]
@employers = @query.blank? ? [] : fetch_employers(@query)
end
Expand All @@ -18,12 +17,12 @@ def summary

@payments = fetch_payroll.map do |payment|
{
employer: payment["employer"],
amount: payment["net_pay"].to_i,
start: payment["paystub_period"]["start_date"],
end: payment["paystub_period"]["end_date"],
hours: payment["hours"],
rate: payment["rate"]
employer: payment["employer_name"],
amount: payment["net_pay_amount"].to_i,
start: payment["pay_period_start"],
end: payment["pay_period_end"],
hours: payment["earnings"][0]["hours"],
rate: payment["earnings"][0]["rate"]
}
end

Expand All @@ -40,7 +39,6 @@ def share

def reset
session[:cbv_flow_id] = nil
session[:argyle_user_token] = nil
redirect_to root_url
end

Expand Down Expand Up @@ -82,32 +80,25 @@ def next_path
end
helper_method :next_path

def fetch_and_store_argyle_token
return session[:argyle_user_token] if session[:argyle_user_token].present?

user_token = provider.create_user

@cbv_flow.update(argyle_user_id: user_token["id"])
session[:argyle_user_token] = user_token["user_token"]

user_token["user_token"]
end

def fetch_employers(query = "")
request_params = {
mapping_status: "verified,mapped",
q: query
q: query,
supported_jobs: [ "paystubs" ]
}

provider.fetch_items(request_params)["results"]
provider.fetch_items(request_params)["data"]
end

def fetch_payroll
provider.fetch_paystubs(user: @cbv_flow.argyle_user_id)["results"]
account_ids = provider.fetch_accounts(end_user_id: @cbv_flow.pinwheel_end_user_id)["data"].map { |account| account["id"] }

account_ids.map do |account_id|
provider.fetch_paystubs(account_id: account_id)["data"]
end.flatten
end

def provider
ArgyleService.new
PinwheelService.new
end

def search_params
Expand Down
29 changes: 0 additions & 29 deletions app/app/controllers/webhooks/argyle/events_controller.rb

This file was deleted.

27 changes: 27 additions & 0 deletions app/app/controllers/webhooks/pinwheel/events_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class Webhooks::Pinwheel::EventsController < ApplicationController
skip_before_action :verify_authenticity_token

def create
signature = request.headers["X-Pinwheel-Signature"]
timestamp = request.headers["X-Timestamp"]

digest = provider.generate_signature_digest(timestamp, request.raw_post)

unless provider.verify_signature(signature, digest)
return render json: { error: "Invalid signature" }, status: :unauthorized
end

if params["event"] == "paystubs.added"
@cbv_flow = CbvFlow.find_by_pinwheel_end_user_id(params["payload"]["end_user_id"])

if @cbv_flow
@cbv_flow.update(payroll_data_available_from: params["payload"]["params"]["from_pay_date"])
PaystubsChannel.broadcast_to(@cbv_flow, params)
end
end
end

def provider
PinwheelService.new
end
end
2 changes: 0 additions & 2 deletions app/app/helpers/webhooks/argyle/events_helper.rb

This file was deleted.

43 changes: 16 additions & 27 deletions app/app/javascript/controllers/cbv_flows_controller.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Controller } from "@hotwired/stimulus"
import * as ActionCable from '@rails/actioncable'

import metaContent from "../utilities/meta";
import { loadArgyle, initializeArgyle, updateToken } from "../utilities/argyle"
import { loadPinwheel, initializePinwheel, fetchToken } from "../utilities/pinwheel"

export default class extends Controller {
static targets = [
Expand All @@ -12,56 +11,46 @@ export default class extends Controller {
"modal"
];

argyle = null;

argyleUserToken = null;
pinwheel = loadPinwheel();

cable = ActionCable.createConsumer();

connect() {
// check for this value when connected
this.argyleUserToken = metaContent('argyle_user_token');
this.cable.subscriptions.create({ channel: 'ArgylePaystubsChannel' }, {
this.cable.subscriptions.create({ channel: 'PaystubsChannel' }, {
connected: () => {
console.log("Connected to the channel:", this);
},
disconnected: () => {
console.log("Disconnected");
},
received: (data) => {
if (data.event === 'paystubs.fully_synced' || data.event === 'paystubs.partially_synced') {
if (data.event === 'paystubs.added') {
this.formTarget.submit();
}
}
});
}

onSignInSuccess(event) {
this.userAccountIdTarget.value = event.accountId;
this.argyle.close();
onSignInSuccess() {
this.pinwheel.then(pinwheel => pinwheel.close());
this.modalTarget.click();
}

onAccountError(event) {
console.log(event);
}

select(event) {
this.submit(event.target.dataset.itemId);
async select(event) {
const { responseType, id } = event.target.dataset;
const { token } = await fetchToken(responseType, id);

this.submit(token);
}

submit(itemId) {
loadArgyle()
.then(Argyle => initializeArgyle(Argyle, this.argyleUserToken, {
items: [itemId],
onAccountConnected: this.onSignInSuccess.bind(this),
onAccountError: this.onAccountError.bind(this),
// Unsure what these are for!
onDDSSuccess: () => { console.log('onDDSSuccess') },
onDDSError: () => { console.log('onDDSSuccess') },
onTokenExpired: updateToken,
}))
.then(argyle => this.argyle = argyle)
.then(() => this.argyle.open());
submit(token) {
this.pinwheel.then(Pinwheel => initializePinwheel(Pinwheel, token, {
onEvent: console.log,
onSuccess: this.onSignInSuccess.bind(this),
}));
}
}
36 changes: 0 additions & 36 deletions app/app/javascript/utilities/argyle.js

This file was deleted.

37 changes: 37 additions & 0 deletions app/app/javascript/utilities/pinwheel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import loadScript from 'load-script';
import metaContent from "./meta";
import CSRF from './csrf';

const PINWHEEL_TOKENS_GENERATE = '/api/pinwheel/tokens';

export function loadPinwheel() {
return new Promise((resolve, reject) => {
loadScript('https://cdn.getpinwheel.com/pinwheel-v3.0.js', (err, script) => {
if (err) {
reject(err);
} else {
resolve(Pinwheel);
}
});
});
}

export function initializePinwheel(Pinwheel, linkToken, callbacks) {
Pinwheel.open({
linkToken,
...callbacks
});

return Pinwheel;
}

export const fetchToken = (response_type, id) => {
return fetch(PINWHEEL_TOKENS_GENERATE, {
method: 'post',
headers: {
'X-CSRF-Token': CSRF.token,
'Content-Type': 'application/json',
},
body: JSON.stringify({ response_type, id }),
}).then(response => response.json());
}
Loading

0 comments on commit c83417a

Please sign in to comment.