From 811cbd4d1d7db1e6725709a97711cfc4839f1185 Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Tue, 9 Mar 2021 08:48:21 +0200 Subject: [PATCH 1/4] Approve payout mutation Signed-off-by: Alexander Ivanov --- .../src/core/graph/schema/payouts/index.ts | 2 ++ .../mutations/ApprovePayout.mutation.ts | 21 +++++++++++++++++++ packages/firebase/package.json | 5 +++++ yarn.lock | 5 +++++ 4 files changed, 33 insertions(+) create mode 100644 packages/firebase/functions/src/core/graph/schema/payouts/mutations/ApprovePayout.mutation.ts diff --git a/packages/firebase/functions/src/core/graph/schema/payouts/index.ts b/packages/firebase/functions/src/core/graph/schema/payouts/index.ts index 4eecc34e3..7beca4ada 100644 --- a/packages/firebase/functions/src/core/graph/schema/payouts/index.ts +++ b/packages/firebase/functions/src/core/graph/schema/payouts/index.ts @@ -8,6 +8,7 @@ import { PayoutStatusEnum } from './enums/PayoutStatus.enum'; import { ExecutePayoutsInput } from './inputs/ExecutePayouts.input'; import { ExecutePayoutsMutation } from './mutations/ExecutePayouts.mutation'; +import { ApprovePayoutMutation } from './mutations/ApprovePayout.mutation'; import { GetPayoutQuery } from './queries/GetPayout.query'; import { GetPayoutsQuery } from './queries/GetPayouts.query'; @@ -23,6 +24,7 @@ export const PayoutTypes = [ ExecutePayoutsInput, ExecutePayoutsMutation, + ApprovePayoutMutation, GetPayoutQuery, GetPayoutsQuery diff --git a/packages/firebase/functions/src/core/graph/schema/payouts/mutations/ApprovePayout.mutation.ts b/packages/firebase/functions/src/core/graph/schema/payouts/mutations/ApprovePayout.mutation.ts new file mode 100644 index 000000000..963101f53 --- /dev/null +++ b/packages/firebase/functions/src/core/graph/schema/payouts/mutations/ApprovePayout.mutation.ts @@ -0,0 +1,21 @@ +import { extendType, idArg, intArg, nonNull, stringArg } from 'nexus'; +import { approvePayout } from '../../../../../circlepay/payouts/business/approvePayout'; + +// http://localhost:5003/common-staging-50741/us-central1/circlepay/payouts/approve?payoutId=d81723d6-ab40-4d26-ba2c-95d75a0197c7&index=0&token=3dda003266fdf9a55b24cf2637cd394c97a2c932874910e2278289b3c6ee345a + +export const ApprovePayoutMutation = extendType({ + type: 'Mutation', + definition(t) { + t.boolean('approvePayout', { + args: { + payoutId: nonNull(idArg()), + index: nonNull(intArg()), + token: nonNull(stringArg()) + }, + + resolve: (root, args) => { + return approvePayout(args); + } + }); + } +}); \ No newline at end of file diff --git a/packages/firebase/package.json b/packages/firebase/package.json index 74c09424e..1866c449b 100644 --- a/packages/firebase/package.json +++ b/packages/firebase/package.json @@ -8,6 +8,10 @@ "compile": "rimraf ./functions/dist/**/* && tsc -p tsconfig.json", "compile:types": "cd ../types && yarn compile", "compile:watch": "rimraf ./functions/dist/**/* && tsc -p tsconfig.json --watch", + "tab:compile:watch": "ttab -t 'Firebase Compilation' yarn compile:watch", + "tab:compile:types:watch": "ttab -t 'Types Compilation' yarn compile:types", + "tab:start": "ttab -t 'Firebase Runtime' yarn start", + "dev": "yarn tab:compile:types:watch && yarn tab:compile:watch && yarn tab:start", "typecheck": "tsc -p tsconfig.json --noEmit", "lint": "eslint ./", "clean": "find ./ -name '*.log' -delete", @@ -66,6 +70,7 @@ "supertest": "^4.0.2", "ts-jest": "^26.4.4", "ts-node-dev": "^1.1.1", + "ttab": "^0.7.0", "typescript": "^4.0.5" }, "private": true, diff --git a/yarn.lock b/yarn.lock index a5649c7a7..47b42af77 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22486,6 +22486,11 @@ tsutils@^3.17.1: dependencies: tslib "^1.8.1" +ttab@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/ttab/-/ttab-0.7.0.tgz#eec30450877ed1b666e522234a47b4561529ecdb" + integrity sha512-JuQeDHCaa1LpeSXv1zmlDfWiXQ86WS/EexjBivyRPug0VdAOvEvgm8qFmmDdVFCIUdvtx0uT6jjiTAV2rb9zKg== + tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" From c78e19988c2b8d2ce2ff80035a7b4c44f662666d Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Tue, 9 Mar 2021 12:25:06 +0200 Subject: [PATCH 2/4] Confirm payout screen Signed-off-by: Alexander Ivanov --- packages/admin/src/graphql/index.ts | 119 +++++++++++++ packages/admin/src/graphql/schema.graphql | 3 +- packages/admin/src/graphql/schema.json | 63 ++++++- .../src/pages/financials/payouts/confirm.tsx | 167 ++++++++++++++++++ .../financials/payouts/details/[payoutId].tsx | 7 +- packages/firebase/env/staging/env_config.json | 4 + .../payouts/business/updatePayoutStatus.ts | 2 +- .../payouts/business/updatePayoutsStatus.ts | 28 +++ .../payouts/crons/payoutStatusCron.ts | 19 +- .../payouts/triggers/onPayoutCreated.ts | 7 +- .../firebase/functions/src/constants/index.ts | 5 + .../src/core/graph/schema/payouts/index.ts | 8 +- .../mutations/UpdatePayoutStatus.mutation.ts | 21 +++ .../mutations/UpdatePayoutsStatus.mutation.ts | 15 ++ packages/firebase/functions/src/index.ts | 2 + .../email/templates/approvePayout.ts | 7 +- .../src/util/middleware/ipMiddleware.ts | 2 +- 17 files changed, 452 insertions(+), 27 deletions(-) create mode 100644 packages/admin/src/pages/financials/payouts/confirm.tsx create mode 100644 packages/firebase/functions/src/circlepay/payouts/business/updatePayoutsStatus.ts create mode 100644 packages/firebase/functions/src/core/graph/schema/payouts/mutations/UpdatePayoutStatus.mutation.ts create mode 100644 packages/firebase/functions/src/core/graph/schema/payouts/mutations/UpdatePayoutsStatus.mutation.ts diff --git a/packages/admin/src/graphql/index.ts b/packages/admin/src/graphql/index.ts index 426c074af..4b3e0e1c8 100644 --- a/packages/admin/src/graphql/index.ts +++ b/packages/admin/src/graphql/index.ts @@ -599,6 +599,7 @@ export type Mutation = { /** Refresh the common members from the events. Returns the new common member count */ refreshCommonMembers?: Maybe; executePayouts?: Maybe; + approvePayout?: Maybe; updatePaymentData?: Maybe; updatePaymentsCommonId?: Maybe; createIntention?: Maybe; @@ -620,6 +621,13 @@ export type MutationExecutePayoutsArgs = { }; +export type MutationApprovePayoutArgs = { + payoutId: Scalars['ID']; + index: Scalars['Int']; + token: Scalars['String']; +}; + + export type MutationUpdatePaymentDataArgs = { id: Scalars['ID']; trackId?: Maybe; @@ -857,6 +865,42 @@ export type UpdatePaymentDataMutation = ( & Pick ); +export type GetConfirmPayoutDataQueryVariables = Exact<{ + payoutId: Scalars['ID']; +}>; + + +export type GetConfirmPayoutDataQuery = ( + { __typename?: 'Query' } + & { payout?: Maybe<( + { __typename?: 'Payout' } + & Pick + & { proposals?: Maybe + & { description: ( + { __typename?: 'ProposalDescription' } + & Pick + ), fundingRequest?: Maybe<( + { __typename?: 'ProposalFunding' } + & Pick + )> } + )>>> } + )> } +); + +export type ApprovePayoutMutationVariables = Exact<{ + payoutId: Scalars['ID']; + token: Scalars['String']; + index: Scalars['Int']; +}>; + + +export type ApprovePayoutMutation = ( + { __typename?: 'Mutation' } + & Pick +); + export type GetProposalsSelectedForBatchQueryVariables = Exact<{ ids: Array | Scalars['String']; }>; @@ -1605,6 +1649,81 @@ export function useUpdatePaymentDataMutation(baseOptions?: Apollo.MutationHookOp export type UpdatePaymentDataMutationHookResult = ReturnType; export type UpdatePaymentDataMutationResult = Apollo.MutationResult; export type UpdatePaymentDataMutationOptions = Apollo.BaseMutationOptions; +export const GetConfirmPayoutDataDocument = gql` + query GetConfirmPayoutData($payoutId: ID!) { + payout(id: $payoutId) { + amount + proposals { + id + description { + title + description + } + fundingRequest { + amount + } + } + } +} + `; + +/** + * __useGetConfirmPayoutDataQuery__ + * + * To run a query within a React component, call `useGetConfirmPayoutDataQuery` and pass it any options that fit your needs. + * When your component renders, `useGetConfirmPayoutDataQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetConfirmPayoutDataQuery({ + * variables: { + * payoutId: // value for 'payoutId' + * }, + * }); + */ +export function useGetConfirmPayoutDataQuery(baseOptions: Apollo.QueryHookOptions) { + return Apollo.useQuery(GetConfirmPayoutDataDocument, baseOptions); + } +export function useGetConfirmPayoutDataLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + return Apollo.useLazyQuery(GetConfirmPayoutDataDocument, baseOptions); + } +export type GetConfirmPayoutDataQueryHookResult = ReturnType; +export type GetConfirmPayoutDataLazyQueryHookResult = ReturnType; +export type GetConfirmPayoutDataQueryResult = Apollo.QueryResult; +export const ApprovePayoutDocument = gql` + mutation ApprovePayout($payoutId: ID!, $token: String!, $index: Int!) { + approvePayout(payoutId: $payoutId, token: $token, index: $index) +} + `; +export type ApprovePayoutMutationFn = Apollo.MutationFunction; + +/** + * __useApprovePayoutMutation__ + * + * To run a mutation, you first call `useApprovePayoutMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useApprovePayoutMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [approvePayoutMutation, { data, loading, error }] = useApprovePayoutMutation({ + * variables: { + * payoutId: // value for 'payoutId' + * token: // value for 'token' + * index: // value for 'index' + * }, + * }); + */ +export function useApprovePayoutMutation(baseOptions?: Apollo.MutationHookOptions) { + return Apollo.useMutation(ApprovePayoutDocument, baseOptions); + } +export type ApprovePayoutMutationHookResult = ReturnType; +export type ApprovePayoutMutationResult = Apollo.MutationResult; +export type ApprovePayoutMutationOptions = Apollo.BaseMutationOptions; export const GetProposalsSelectedForBatchDocument = gql` query getProposalsSelectedForBatch($ids: [String!]!) { proposals(ids: $ids) { diff --git a/packages/admin/src/graphql/schema.graphql b/packages/admin/src/graphql/schema.graphql index 53c66504c..1007624db 100644 --- a/packages/admin/src/graphql/schema.graphql +++ b/packages/admin/src/graphql/schema.graphql @@ -409,7 +409,8 @@ type Mutation { commonId: ID! ): Int executePayouts(input: ExecutePayoutInput!): Payout - updatePaymentData(id: ID!, trackId: ID = "5f398531-0f9b-42ec-8dd0-99896ac47e74"): Boolean + approvePayout(payoutId: ID!, index: Int!, token: String!): Boolean + updatePaymentData(id: ID!, trackId: ID = "7f4fe0bd-590a-455d-b3cd-fe6045d36066"): Boolean updatePaymentsCommonId: Boolean createIntention(input: CreateIntentionInput!): Intention } \ No newline at end of file diff --git a/packages/admin/src/graphql/schema.json b/packages/admin/src/graphql/schema.json index 975c1b871..070a5174e 100644 --- a/packages/admin/src/graphql/schema.json +++ b/packages/admin/src/graphql/schema.json @@ -4188,6 +4188,67 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "approvePayout", + "description": null, + "args": [ + { + "name": "payoutId", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "index", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "token", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "updatePaymentData", "description": null, @@ -4216,7 +4277,7 @@ "name": "ID", "ofType": null }, - "defaultValue": "\"f9ba4c76-91f6-4f8d-83d0-278cc034f77a\"", + "defaultValue": "\"8185aa06-1fe0-44e7-936e-cd262fdc22f9\"", "isDeprecated": false, "deprecationReason": null } diff --git a/packages/admin/src/pages/financials/payouts/confirm.tsx b/packages/admin/src/pages/financials/payouts/confirm.tsx new file mode 100644 index 000000000..db00a8805 --- /dev/null +++ b/packages/admin/src/pages/financials/payouts/confirm.tsx @@ -0,0 +1,167 @@ +import { gql } from '@apollo/client'; +import { useRouter } from 'next/router'; +import { useGetConfirmPayoutDataLazyQuery, useApprovePayoutMutation } from '@graphql'; +import React from 'react'; +import { Link } from '@components/Link'; +import { Breadcrumbs, Button, Spacer, Text, useToasts } from '@geist-ui/react'; +import Skeleton from 'react-loading-skeleton'; + +const ConfirmPayoutPageData = gql` + query GetConfirmPayoutData($payoutId: ID!) { + payout(id: $payoutId) { + amount + + proposals { + id + + description { + title + description + } + + fundingRequest { + amount + } + } + } + } +`; + +const ApprovePayout = gql` + mutation ApprovePayout($payoutId: ID!, $token: String!, $index: Int!) { + approvePayout( + payoutId: $payoutId, + token: $token, + index: $index + ) + } +`; + +const ConfirmPayoutPage = () => { + const router = useRouter(); + + const [toasts, setToast] = useToasts(); + const [loadData, { data }] = useGetConfirmPayoutDataLazyQuery(); + const [approvePayout, { data: approvePayoutResult, loading: approveLoading }] = useApprovePayoutMutation(); + + React.useEffect(() => { + if (router.query.payoutId) { + loadData({ + variables: { + payoutId: router.query.payoutId as string + } + }); + } + }, [router.query.payoutId]); + + const onPayoutApprove = async () => { + const { payoutId, index, token } = router.query; + + if ( + typeof payoutId === 'undefined' || + typeof index === 'undefined' || + typeof token === 'undefined' + ) { + setToast({ + text: 'Invalid approve payout request! There is missing data', + type: 'error' + }); + + return; + } + + const res = await approvePayout({ + variables: { + payoutId: payoutId as string, + token: token as string, + index: Number(index) + } + }); + + if (res) { + setToast({ + text: 'Successfully approved payout' + }); + } + + router.push(`/financials/payouts/details/${payoutId}`); + }; + + + return ( + + Confirm Payout + + + Home + + + Financials + + + + Payouts + + + + Confirm + + + + [{router.query.payoutId}] + + + + In Total: {!data + ? + : (data.payout.amount / 100).toLocaleString('en-US', { + style: 'currency', + currency: 'USD' + })} + + + Proposals in this payout: + + {data && ( + + {data.payout.proposals.map((p, i) => ( + + {i + 1}. {p.description.title}: {(p.fundingRequest.amount / 100).toLocaleString('en-US', { + style: 'currency', + currency: 'USD' + })} + + ))} + + )} + + + Actions + + + + + + + ); +}; + +export default ConfirmPayoutPage; \ No newline at end of file diff --git a/packages/admin/src/pages/financials/payouts/details/[payoutId].tsx b/packages/admin/src/pages/financials/payouts/details/[payoutId].tsx index 669312551..d703f9f7d 100644 --- a/packages/admin/src/pages/financials/payouts/details/[payoutId].tsx +++ b/packages/admin/src/pages/financials/payouts/details/[payoutId].tsx @@ -77,8 +77,13 @@ const PayoutDetailsPage: NextPage = () => { )} Home + + + Financials + + - Payouts + Payouts diff --git a/packages/firebase/env/staging/env_config.json b/packages/firebase/env/staging/env_config.json index 694c197d2..313ae3f40 100644 --- a/packages/firebase/env/staging/env_config.json +++ b/packages/firebase/env/staging/env_config.json @@ -40,5 +40,9 @@ "local": "http://localhost:5003/common-staging-50741/us-central1", "backoffice":{ "sheetUrl": "1muC-dGhS_MOEZYKSTNyMthG1NHlo8-4mlb_K5ExD7QM" + }, + "admin": { + "local": "http://localhost:3000", + "url": "http://localhost:3000" } } diff --git a/packages/firebase/functions/src/circlepay/payouts/business/updatePayoutStatus.ts b/packages/firebase/functions/src/circlepay/payouts/business/updatePayoutStatus.ts index ba05cca95..1fcc61cac 100644 --- a/packages/firebase/functions/src/circlepay/payouts/business/updatePayoutStatus.ts +++ b/packages/firebase/functions/src/circlepay/payouts/business/updatePayoutStatus.ts @@ -32,7 +32,7 @@ export const updatePayoutStatus = async (payout: IPayoutEntity): Promise = )).data; }, { errorCode: ErrorCodes.CirclePayError, - userMessage: 'Cannot create the bank account, because it was rejected by Circle' + userMessage: 'Cannot get the payout from circle' }); // If the status have changed broadcast the event diff --git a/packages/firebase/functions/src/circlepay/payouts/business/updatePayoutsStatus.ts b/packages/firebase/functions/src/circlepay/payouts/business/updatePayoutsStatus.ts new file mode 100644 index 000000000..733dab6ce --- /dev/null +++ b/packages/firebase/functions/src/circlepay/payouts/business/updatePayoutsStatus.ts @@ -0,0 +1,28 @@ +import { payoutDb } from '../database'; +import { updatePayoutStatus } from './updatePayoutStatus'; + +/** + * Updates the status of all pending payouts from circle + */ +export const updatePayoutsStatus = async () => { + const pendingPayouts = await payoutDb.getMany({ + status: 'pending' + }); + + if (pendingPayouts && pendingPayouts.length) { + for (const payout of pendingPayouts) { + logger.info(`Updating the status of payout ${ payout.id }`); + + try { + // eslint-disable-next-line no-await-in-loop + await updatePayoutStatus(payout); + } catch (e) { + console.log(e); + + logger.error('Unable to update the status of payout', { + payout + }); + } + } + } +}; \ No newline at end of file diff --git a/packages/firebase/functions/src/circlepay/payouts/crons/payoutStatusCron.ts b/packages/firebase/functions/src/circlepay/payouts/crons/payoutStatusCron.ts index b82720052..90d907e46 100644 --- a/packages/firebase/functions/src/circlepay/payouts/crons/payoutStatusCron.ts +++ b/packages/firebase/functions/src/circlepay/payouts/crons/payoutStatusCron.ts @@ -1,24 +1,9 @@ import * as functions from 'firebase-functions'; -import { payoutDb } from '../database'; -import { updatePayoutStatus } from '../business/updatePayoutStatus'; +import { updatePayoutsStatus } from '../business/updatePayoutsStatus'; // Update the payout statuses every 12 hours export const payoutStatusCron = functions.pubsub .schedule('0 */12 * * *') .onRun(async () => { - const pendingPayouts = await payoutDb.getMany({ - status: 'pending' - }); - - if (pendingPayouts && pendingPayouts.length) { - const promiseArr: Promise[] = []; - - pendingPayouts.forEach(payout => { - promiseArr.push((async () => { - logger.info(`Updating the status of payout ${payout.id}`); - - await updatePayoutStatus(payout); - })()); - }); - } + await updatePayoutsStatus(); }); diff --git a/packages/firebase/functions/src/circlepay/payouts/triggers/onPayoutCreated.ts b/packages/firebase/functions/src/circlepay/payouts/triggers/onPayoutCreated.ts index e2741057e..1283dda84 100644 --- a/packages/firebase/functions/src/circlepay/payouts/triggers/onPayoutCreated.ts +++ b/packages/firebase/functions/src/circlepay/payouts/triggers/onPayoutCreated.ts @@ -30,6 +30,10 @@ export const onPayoutCreated: IEventTrigger = async (eventObj) => { ? env.local : env.endpoints.base; + const adminUrl = process.env.NODE_ENV === 'dev' + ? env.admin.local + : env.admin.url; + await emailClient.sendTemplatedEmail({ templateKey: 'approvePayout', to: approver, @@ -51,7 +55,8 @@ export const onPayoutCreated: IEventTrigger = async (eventObj) => { payoutId: payout.id, amount: (payout.amount / 100).toLocaleString('en-US', { style: 'currency', currency: 'USD' }), - url: `${ urlBase }/circlepay/payouts/approve?payoutId=${ payout.id }&index=${ index }&token=${ payout.security[index].token }` + url: `${ urlBase }/circlepay/payouts/approve?payoutId=${ payout.id }&index=${ index }&token=${ payout.security[index].token }`, + adminUrl: `${ adminUrl }/financials/payouts/confirm?payoutId=${ payout.id }&index=${ index }&token=${ payout.security[index].token }` } }); })); diff --git a/packages/firebase/functions/src/constants/index.ts b/packages/firebase/functions/src/constants/index.ts index 7de36ac3c..a6e55d8c9 100644 --- a/packages/firebase/functions/src/constants/index.ts +++ b/packages/firebase/functions/src/constants/index.ts @@ -52,6 +52,11 @@ interface Env { secretManagerProject: string; local: string; + admin: { + local: string, + url: string + } + payouts: { approvers: string[]; neededApprovals: number; diff --git a/packages/firebase/functions/src/core/graph/schema/payouts/index.ts b/packages/firebase/functions/src/core/graph/schema/payouts/index.ts index 7beca4ada..8b25823eb 100644 --- a/packages/firebase/functions/src/core/graph/schema/payouts/index.ts +++ b/packages/firebase/functions/src/core/graph/schema/payouts/index.ts @@ -7,8 +7,10 @@ import { PayoutStatusEnum } from './enums/PayoutStatus.enum'; import { ExecutePayoutsInput } from './inputs/ExecutePayouts.input'; -import { ExecutePayoutsMutation } from './mutations/ExecutePayouts.mutation'; import { ApprovePayoutMutation } from './mutations/ApprovePayout.mutation'; +import { ExecutePayoutsMutation } from './mutations/ExecutePayouts.mutation'; +import { UpdatePayoutStatusMutation } from './mutations/UpdatePayoutStatus.mutation'; +import { UpdatePayoutsStatusMutation } from './mutations/UpdatePayoutsStatus.mutation'; import { GetPayoutQuery } from './queries/GetPayout.query'; import { GetPayoutsQuery } from './queries/GetPayouts.query'; @@ -23,8 +25,10 @@ export const PayoutTypes = [ ExecutePayoutsInput, - ExecutePayoutsMutation, ApprovePayoutMutation, + ExecutePayoutsMutation, + UpdatePayoutStatusMutation, + UpdatePayoutsStatusMutation, GetPayoutQuery, GetPayoutsQuery diff --git a/packages/firebase/functions/src/core/graph/schema/payouts/mutations/UpdatePayoutStatus.mutation.ts b/packages/firebase/functions/src/core/graph/schema/payouts/mutations/UpdatePayoutStatus.mutation.ts new file mode 100644 index 000000000..2ae1b2d38 --- /dev/null +++ b/packages/firebase/functions/src/core/graph/schema/payouts/mutations/UpdatePayoutStatus.mutation.ts @@ -0,0 +1,21 @@ +import { extendType, idArg, nonNull } from 'nexus'; +import { updatePayoutStatus } from '../../../../../circlepay/payouts/business/updatePayoutStatus'; +import { payoutDb } from '../../../../../circlepay/payouts/database'; + +export const UpdatePayoutStatusMutation = extendType({ + type: 'Mutation', + definition(t) { + t.boolean('updatePayoutStatus', { + args: { + payoutId: nonNull(idArg()) + }, + resolve: async (root, { payoutId }) => { + await updatePayoutStatus( + await payoutDb.get(payoutId) + ); + + return true; + } + }); + } +}); \ No newline at end of file diff --git a/packages/firebase/functions/src/core/graph/schema/payouts/mutations/UpdatePayoutsStatus.mutation.ts b/packages/firebase/functions/src/core/graph/schema/payouts/mutations/UpdatePayoutsStatus.mutation.ts new file mode 100644 index 000000000..80920724b --- /dev/null +++ b/packages/firebase/functions/src/core/graph/schema/payouts/mutations/UpdatePayoutsStatus.mutation.ts @@ -0,0 +1,15 @@ +import { extendType } from 'nexus'; +import { updatePayoutsStatus } from '../../../../../circlepay/payouts/business/updatePayoutsStatus'; + +export const UpdatePayoutsStatusMutation = extendType({ + type: 'Mutation', + definition(t) { + t.boolean('updatePayoutsStatus', { + resolve: async () => { + await updatePayoutsStatus(); + + return true; + } + }); + } +}); \ No newline at end of file diff --git a/packages/firebase/functions/src/index.ts b/packages/firebase/functions/src/index.ts index 8f93331c5..1036ba560 100644 --- a/packages/firebase/functions/src/index.ts +++ b/packages/firebase/functions/src/index.ts @@ -23,6 +23,7 @@ import { graphApp } from './core'; import { permissionApp } from './permissions'; import { dailySubscriptionCron } from './subscriptions/cron/dailySubscriptionCron'; import { moderationApp } from './moderation'; +import { payoutStatusCron } from './circlepay/payouts/crons'; // --- Express apps export const commons = commonsApp; @@ -54,3 +55,4 @@ exports.dailySubscriptionsCron = dailySubscriptionCron; exports.cronJobs = cron; exports.circlePayCrons = circlePayCrons; exports.circleBalanceCrons = circleBalanceCrons; +exports.payoutsStatusCron = payoutStatusCron; diff --git a/packages/firebase/functions/src/notification/email/templates/approvePayout.ts b/packages/firebase/functions/src/notification/email/templates/approvePayout.ts index 47db09fd3..efcb5bcd4 100644 --- a/packages/firebase/functions/src/notification/email/templates/approvePayout.ts +++ b/packages/firebase/functions/src/notification/email/templates/approvePayout.ts @@ -16,8 +16,8 @@ const template = ` Description: {{bankDescription}}


- You can approve payout {{payoutId}} for {{amount}} by clicking here. Please note - that for doing the proposal there will be $25 fee charged by the payout provider. + You can approve payout {{payoutId}} for {{amount}} by clicking here to approve immediately or + here for more options.

Have a nice day,
@@ -35,6 +35,9 @@ const emailStubs = { url: { required: true }, + adminUrl: { + required: true + }, beneficiary: { required: true }, diff --git a/packages/firebase/functions/src/util/middleware/ipMiddleware.ts b/packages/firebase/functions/src/util/middleware/ipMiddleware.ts index 1848c8161..3e4ebc22f 100644 --- a/packages/firebase/functions/src/util/middleware/ipMiddleware.ts +++ b/packages/firebase/functions/src/util/middleware/ipMiddleware.ts @@ -7,7 +7,7 @@ export const ipMiddleware: RequestHandler = (req, res, next) => { req.ipAddress = userIp || forwardedFor || '127.0.0.1'; if (req.ipAddress === '127.0.0.1') { - logger.warn('Cannot parse the IP address for request', { headers: req.headers }); + // logger.warn('Cannot parse the IP address for request', { headers: req.headers }); } next(); From 0a30d9bb397739a6a85aea41e4897fae7c933c09 Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Tue, 9 Mar 2021 15:56:34 +0200 Subject: [PATCH 3/4] Small bug Signed-off-by: Alexander Ivanov --- packages/admin/src/pages/financials/payments/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/admin/src/pages/financials/payments/index.tsx b/packages/admin/src/pages/financials/payments/index.tsx index 3b157254c..44c91c474 100644 --- a/packages/admin/src/pages/financials/payments/index.tsx +++ b/packages/admin/src/pages/financials/payments/index.tsx @@ -113,7 +113,7 @@ export const PaymentsHomepage: NextPage = () => { - Hanging payments ({payments?.hangingPayments}) + Hanging payments ({payments?.hangingPayments.length}) {(payments?.hangingPayments).map((payment) => ( From 12b47940c97f1a705f5f95821a94383e93eb83ab Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Thu, 11 Mar 2021 08:13:51 +0200 Subject: [PATCH 4/4] Fix EsLint --- .../src/circlepay/payouts/business/updatePayoutsStatus.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/firebase/functions/src/circlepay/payouts/business/updatePayoutsStatus.ts b/packages/firebase/functions/src/circlepay/payouts/business/updatePayoutsStatus.ts index 733dab6ce..a82884097 100644 --- a/packages/firebase/functions/src/circlepay/payouts/business/updatePayoutsStatus.ts +++ b/packages/firebase/functions/src/circlepay/payouts/business/updatePayoutsStatus.ts @@ -4,7 +4,7 @@ import { updatePayoutStatus } from './updatePayoutStatus'; /** * Updates the status of all pending payouts from circle */ -export const updatePayoutsStatus = async () => { +export const updatePayoutsStatus = async (): Promise => { const pendingPayouts = await payoutDb.getMany({ status: 'pending' }); @@ -17,8 +17,6 @@ export const updatePayoutsStatus = async () => { // eslint-disable-next-line no-await-in-loop await updatePayoutStatus(payout); } catch (e) { - console.log(e); - logger.error('Unable to update the status of payout', { payout });