11/* eslint-disable import/prefer-default-export */
22import type { Orders } from '@cloudcommerce/types' ;
3+ import type { AxiosError } from 'axios' ;
34import api from '@cloudcommerce/api' ;
45import '@cloudcommerce/firebase/lib/init' ;
56import * as functions from 'firebase-functions/v1' ;
67import config , { logger } from '@cloudcommerce/firebase/lib/config' ;
78import getAppData from '@cloudcommerce/firebase/lib/helpers/get-app-data' ;
9+ import { getAsaasAxios } from './util/asaas-api' ;
810
911type PaymentEntry = Exclude < Orders [ 'payments_history' ] , undefined > [ 0 ]
1012
11- const { httpsFunctionOptions } = config . get ( ) ;
13+ const {
14+ storeId,
15+ httpsFunctionOptions,
16+ } = config . get ( ) ;
17+
18+ const setAsaasEnv = async ( ) => {
19+ if ( ! process . env . ASAAS_API_KEY ) {
20+ const appData = await getAppData ( 'asaas' ) ;
21+ process . env . ASAAS_API_KEY = appData . asaas_api_key ;
22+ if ( appData . asaas_sandbox ) {
23+ process . env . ASAAS_ENV = 'sandbox' ;
24+ }
25+ }
26+ } ;
27+
28+ const parseAsaasStatus = ( asaasStatus : string ) : PaymentEntry [ 'status' ] | null => {
29+ switch ( asaasStatus ) {
30+ case 'CREDIT_CARD_CAPTURE_REFUSED' :
31+ return 'unauthorized' ;
32+ case 'AWAITING_CHARGEBACK_REVERSAL' :
33+ case 'CHARGEBACK_DISPUTE' :
34+ case 'CHARGEBACK_REQUESTED' :
35+ return 'in_dispute' ;
36+ case 'RECEIVED_IN_CASH_UNDONE' :
37+ case 'DELETED' :
38+ return 'voided' ;
39+ case 'REFUND_IN_PROGRESS' :
40+ case 'REFUNDED' :
41+ return 'refunded' ;
42+ case 'PENDING' :
43+ case 'RESTORED' :
44+ return 'pending' ;
45+ case 'RECEIVED' :
46+ case 'CONFIRMED' :
47+ return 'paid' ;
48+ case 'REPROVED_BY_RISK_ANALYSIS' :
49+ return 'unauthorized' ;
50+ case 'AWAITING_RISK_ANALYSIS' :
51+ return 'under_analysis' ;
52+ default :
53+ return null ;
54+ }
55+ } ;
56+
57+ const updatePaymentStatus = async ( {
58+ orderId,
59+ transactionId,
60+ asaasStatus,
61+ flag = 'webhook' ,
62+ } : {
63+ orderId : Orders [ '_id' ] ,
64+ transactionId ?: string ,
65+ asaasStatus : string ,
66+ flag ?: string ,
67+ } ) => {
68+ const status = parseAsaasStatus ( asaasStatus ) ;
69+ if ( ! status ) {
70+ logger . warn ( `Unexpected Asaas status for ${ orderId } ` , { asaasStatus } ) ;
71+ return ;
72+ }
73+ await api . post ( `orders/${ orderId } /payments_history` , {
74+ date_time : new Date ( ) . toISOString ( ) ,
75+ status,
76+ transaction_id : transactionId ,
77+ flags : [ 'asaas' , flag , asaasStatus ] ,
78+ } ) ;
79+ logger . info ( `Updated ${ orderId } to ${ status } ` ) ;
80+ } ;
1281
1382export const asaas = {
1483 webhook : functions
1584 . region ( httpsFunctionOptions . region )
1685 . runWith ( httpsFunctionOptions )
1786 . https . onRequest ( async ( req , res ) => {
1887 const { body, headers } = req ;
19- const asaasStatus = body ?. event ;
88+ const asaasStatus = ( body ?. event as string | undefined ) ?. replace ( / ^ P A Y M E N T _ / , '' ) ;
2089 const asaasPaymentId = body ?. payment ?. id ;
2190 if ( req . method !== 'POST' || ! asaasStatus || ! asaasPaymentId ) {
2291 res . sendStatus ( 405 ) ;
2392 return ;
2493 }
25- if ( ! process . env . ASAAS_API_KEY ) {
26- const appData = await getAppData ( 'asaas' ) ;
27- process . env . ASAAS_API_KEY = appData . asaas_api_key ;
28- if ( appData . asaas_sandbox ) {
29- process . env . ASAAS_ENV = 'sandbox' ;
30- }
31- }
94+ await setAsaasEnv ( ) ;
3295 const { ASAAS_API_KEY } = process . env ;
3396 if ( ! ASAAS_API_KEY ) {
3497 res . sendStatus ( 403 ) ;
3598 return ;
3699 }
37- if ( headers [ 'asaas-access-token' ] !== `w1_${ ASAAS_API_KEY } ` ) {
100+ const asaasKeyId = `${ ASAAS_API_KEY } ` . substring ( 0 , 6 ) + `${ ASAAS_API_KEY } ` . slice ( - 3 ) ;
101+ if ( headers [ 'asaas-access-token' ] !== `${ storeId } _${ asaasKeyId } ` ) {
38102 logger . warn ( 'Unauthorized webhook' , {
39- headers,
103+ asaasStatus,
104+ asaasPaymentId,
40105 } ) ;
41106 res . sendStatus ( 401 ) ;
42107 return ;
43108 }
44109 logger . info ( `Asaas webhook ${ asaasPaymentId } ${ asaasStatus } ` ) ;
45- let status : PaymentEntry [ 'status' ] = 'pending' ;
46- switch ( asaasStatus ) {
47- case 'PAYMENT_CREDIT_CARD_CAPTURE_REFUSED' :
48- status = 'unauthorized' ;
49- break ;
50- case 'PAYMENT_AWAITING_CHARGEBACK_REVERSAL' :
51- case 'PAYMENT_CHARGEBACK_DISPUTE' :
52- case 'PAYMENT_CHARGEBACK_REQUESTED' :
53- status = 'in_dispute' ;
54- break ;
55- case 'PAYMENT_RECEIVED_IN_CASH_UNDONE' :
56- case 'PAYMENT_DELETED' :
57- status = 'voided' ;
58- break ;
59- case 'PAYMENT_REFUND_IN_PROGRESS' :
60- case 'PAYMENT_REFUNDED' :
61- status = 'refunded' ;
62- break ;
63- case 'PAYMENT_RESTORED' :
64- status = 'pending' ;
65- break ;
66- case 'PAYMENT_RECEIVED' :
67- case 'PAYMENT_CONFIRMED' :
68- status = 'paid' ;
69- break ;
70- case 'PAYMENT_REPROVED_BY_RISK_ANALYSIS' :
71- status = 'unauthorized' ;
72- break ;
73- case 'PAYMENT_AWAITING_RISK_ANALYSIS' :
74- status = 'under_analysis' ;
75- break ;
76- default :
77- // Ignore unknow status
78- return ;
110+ const status = parseAsaasStatus ( asaasStatus ) ;
111+ if ( ! status ) {
112+ res . sendStatus ( 204 ) ;
113+ return ;
79114 }
80115 const {
81116 data : { result : [ order ] } ,
@@ -94,16 +129,70 @@ export const asaas = {
94129 return intermediator ?. transaction_id === String ( asaasPaymentId ) ;
95130 } ) ;
96131 if ( transaction ?. _id ) {
97- await api . post ( `orders/${ order . _id } /payments_history` , {
98- date_time : new Date ( ) . toISOString ( ) ,
99- status,
100- transaction_id : transaction . _id ,
101- flags : [ 'asaas' , asaasStatus ] ,
132+ await updatePaymentStatus ( {
133+ orderId : order . _id ,
134+ transactionId : transaction . _id ,
135+ asaasStatus,
102136 } ) ;
103- logger . info ( `Updated ${ order . _id } to ${ status } ` ) ;
104137 res . sendStatus ( 201 ) ;
105138 return ;
106139 }
107140 res . sendStatus ( 200 ) ;
108141 } ) ,
142+
143+ cronCheckPayments : functions
144+ . region ( config . get ( ) . httpsFunctionOptions . region )
145+ . runWith ( { timeoutSeconds : 540 } )
146+ . pubsub . schedule ( process . env . CRONTAB_ASAAS_CHECK_PAYMENTS || '28 15,2 * * *' )
147+ . timeZone ( 'America/Sao_Paulo' )
148+ . onRun ( async ( ) => {
149+ await setAsaasEnv ( ) ;
150+ const { ASAAS_API_KEY } = process . env ;
151+ if ( ! ASAAS_API_KEY ) return ;
152+ const d = new Date ( ) ;
153+ const isOddHourExec = ! ! ( d . getHours ( ) % 2 ) ;
154+ d . setDate ( d . getDate ( ) - 30 ) ;
155+ const endpoint = 'orders'
156+ + '?fields=_id,transactions'
157+ + '&transactions.app.intermediator.code=asaas3'
158+ + '&financial_status.current=pending'
159+ + `&created_at>=${ d . toISOString ( ) } `
160+ + `&sort=${ ( isOddHourExec ? '' : '-' ) } number`
161+ + '&limit=500' as `orders?${string } `;
162+ const { data : { result : orders } } = await api . get ( endpoint ) ;
163+ logger . info ( `${ orders . length } orders listed` , {
164+ orderIds : orders . map ( ( { _id } ) => _id ) ,
165+ } ) ;
166+ const asaasAxios = await getAsaasAxios ( ) ;
167+ for ( let i = 0 ; i < orders . length ; i ++ ) {
168+ const order = orders [ i ] ;
169+ const transaction = order . transactions ?. find ( ( { app } ) => {
170+ return app ?. intermediator ?. code === 'asaas3' ;
171+ } ) ;
172+ const asaasPaymentId = transaction ?. intermediator ?. transaction_id ;
173+ if ( ! asaasPaymentId ) continue ;
174+ let asaasStatus : string | undefined ;
175+ try {
176+ // eslint-disable-next-line no-await-in-loop
177+ const { data } = await asaasAxios . get ( `/v3/payments/${ asaasPaymentId } /status` ) ;
178+ asaasStatus = data . status ;
179+ } catch ( _err : any ) {
180+ const err : AxiosError = _err ;
181+ const status = err . response ?. status ;
182+ logger . warn ( `Failed with ${ status } reading Asaas status for ${ order . _id } ` ) ;
183+ if ( status === 404 || status === 400 ) continue ;
184+ logger . error ( err ) ;
185+ break ;
186+ }
187+ if ( ! asaasStatus ) continue ;
188+ const status = parseAsaasStatus ( asaasStatus ) ;
189+ if ( ! status || status === 'pending' ) continue ;
190+ updatePaymentStatus ( {
191+ orderId : order . _id ,
192+ transactionId : transaction . _id ,
193+ asaasStatus,
194+ flag : 'cron' ,
195+ } ) ;
196+ }
197+ } ) ,
109198} ;
0 commit comments