Skip to content

Commit

Permalink
#955 withdraw transaction failed (#1013)
Browse files Browse the repository at this point in the history
* fix: create function to sync feed events with blockchain receipts, fix the colors for different statuses of withdraw event

* fix: get rid of useless otplStatus variable

* fix: fix color for success withdraw receive event

* fix: update feed event with receipt on getFeedPage

* fix: handle case of getFeedPage when get(byid).get(id) is undefined - get the receipt and update event

* fix: if item is undefined then set updatedEvent as new value

* fix: show errored withdraw feed card like failed transaction

* fix: save txHash in outer scope and use it iside onError callback

* fix: update snapshots

* fix: fix styles for errored card

* fix: update snapshots

* add: handle send by qr error

* add: verify txhash is defined

* refactor: correct flow type return value

* fix: restore otpl in PR #1003
  • Loading branch information
sirpy committed Dec 1, 2019
1 parent 6d1532f commit 68e345c
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 36 deletions.
9 changes: 8 additions & 1 deletion src/components/dashboard/Claim.js
Expand Up @@ -205,8 +205,13 @@ const Claim = props => {
if (curEntitlement == 0) {
return
}

let txHash

const receipt = await goodWallet.claim({
onTransactionHash: hash => {
txHash = hash

const date = new Date()
const transactionEvent: TransactionEvent = {
id: hash,
Expand All @@ -220,7 +225,9 @@ const Claim = props => {
userStorage.enqueueTX(transactionEvent)
AsyncStorage.setItem('GD_AddWebAppLastClaim', date.toISOString())
},
onError: userStorage.markWithErrorEvent,
onError: () => {
userStorage.markWithErrorEvent(txHash)
},
})

if (receipt.status) {
Expand Down
15 changes: 15 additions & 0 deletions src/components/dashboard/FeedItems/EventSettingsByType.js
Expand Up @@ -61,6 +61,21 @@ const getEventSettingsByType = (theme, type) => {
color: theme.colors.green,
name: 'receive-filled',
},
withdrawerror: {
color: theme.colors.primary,
name: 'info',
withoutAmount: true,
},
withdrawcompleted: {
actionSymbol: '+',
color: theme.colors.green,
name: 'receive-filled',
},
withdrawpending: {
actionSymbol: '+',
color: theme.colors.orange,
name: 'receive-filled',
},
message: {
color: theme.colors.purple,
name: 'social-good-filled',
Expand Down
2 changes: 1 addition & 1 deletion src/components/dashboard/FeedItems/FeedModalItem.js
Expand Up @@ -42,7 +42,7 @@ const FeedModalItem = (props: FeedEventProps) => {
>
{item.type === 'feedback' ? (
<FeedbackModalItem {...props} />
) : itemType === 'senderror' ? (
) : ['senderror', 'withdrawerror'].includes(itemType) ? (
<SendModalItemWithError {...props} />
) : (
<React.Fragment>
Expand Down
6 changes: 4 additions & 2 deletions src/components/dashboard/FeedItems/ListEventItem.js
Expand Up @@ -26,6 +26,7 @@ const ListEvent = ({ item: feed, theme, styles }: FeedEventProps) => {
const mainColor = eventSettings.color
const isSmallDevice = isMobile && getScreenWidth() < 353
const isFeedTypeClaiming = feed.type === 'claiming'
const isErrorCard = ['senderror', 'withdrawerror'].includes(itemType)

if (itemType === 'empty') {
return <EmptyEventFeed />
Expand Down Expand Up @@ -64,7 +65,7 @@ const ListEvent = ({ item: feed, theme, styles }: FeedEventProps) => {
source={feed.data && feed.data.endpoint && feed.data.endpoint.avatar}
/>
<View style={[styles.mainInfo, isFeedTypeClaiming && styles.claimingCardFeedText]}>
{itemType === 'senderror' ? (
{isErrorCard ? (
<>
<Text fontWeight="medium" lineHeight={19} style={styles.mainText} color="primary">
{`We're sorry.`}
Expand Down Expand Up @@ -107,7 +108,7 @@ const getWelcomeStyles = ({ theme }) => ({
display: 'inline',
},
welcomeText: {
flexShrink: 0,
textAlign: 'left',
},
})

Expand Down Expand Up @@ -248,6 +249,7 @@ const getStylesFromProps = ({ theme }) => ({
},
mainText: {
textAlignVertical: 'middle',
paddingTop: 5,
},
})

Expand Down
2 changes: 1 addition & 1 deletion src/components/dashboard/FeedList.js
Expand Up @@ -110,7 +110,7 @@ const FeedList = ({
})
.catch(e => {
showErrorDialog('Canceling the payment link has failed', e)
userStorage.recoverEvent(id)
userStorage.updateOTPLEventStatus(id, 'pending')
})
}
}
Expand Down
11 changes: 10 additions & 1 deletion src/components/dashboard/SendLinkSummary.js
Expand Up @@ -109,6 +109,8 @@ const SendLinkSummary = ({ screenProps }: AmountProps) => {
*/
const generateLink = () => {
try {
let txHash

// Generate link deposit
const generateLinkResponse = goodWallet.generateLink(
amount,
Expand Down Expand Up @@ -141,7 +143,14 @@ const SendLinkSummary = ({ screenProps }: AmountProps) => {
})
}
},
{ onError: userStorage.markWithErrorEvent }
{
onTransactionHash: _h => {
txHash = _h
},
onError: () => {
userStorage.markWithErrorEvent(txHash)
},
}
)
log.debug('generateLinkAndSend:', { generateLinkResponse })
if (generateLinkResponse) {
Expand Down
9 changes: 3 additions & 6 deletions src/components/dashboard/SendQRSummary.js
Expand Up @@ -57,9 +57,11 @@ const SendQRSummary = ({ screenProps }: AmountProps) => {
const sendGD = () => {
try {
setLoading(true)
let txhash
goodWallet.sendAmount(to, amount, {
onTransactionHash: hash => {
log.debug({ hash })
txhash = hash

// Save transaction
const transactionEvent: TransactionEvent = {
Expand Down Expand Up @@ -92,12 +94,7 @@ const SendQRSummary = ({ screenProps }: AmountProps) => {
},
onError: e => {
log.error('Send TX failed:', e.message, e)
showDialog({
visible: true,
title: 'Transaction Failed!',
message: `There was a problem sending G$. Try again`,
dismissText: 'OK',
})
userStorage.markWithErrorEvent(txhash)
},
})
} catch (e) {
Expand Down
86 changes: 64 additions & 22 deletions src/lib/gundb/UserStorageClass.js
Expand Up @@ -26,6 +26,7 @@ import { getUserModel, type UserModel } from './UserModel'

const logger = pino.child({ from: 'UserStorage' })

const EVENT_TYPE_WITHDRAW = 'withdraw'
const EVENT_TYPE_BONUS = 'bonus'
const EVENT_TYPE_CLAIM = 'claim'
const EVENT_TYPE_SEND = 'send'
Expand Down Expand Up @@ -650,7 +651,7 @@ export class UserStorage {
const updatedFeedEvent: FeedEvent = {
...feedEvent,
...initialEvent,
status: feedEvent.status === 'cancelled' ? feedEvent.status : receipt.status ? 'completed' : 'error',
status: feedEvent.otplStatus === 'cancelled' ? feedEvent.status : receipt.status ? 'completed' : 'error',
date: receiptDate.toString(),
data: {
...feedEvent.data,
Expand Down Expand Up @@ -1217,12 +1218,25 @@ export class UserStorage {
return Promise.all(
eventsIndex
.filter(_ => _.id)
.map(eventIndex =>
this.feed
.map(async eventIndex => {
let item = this.feed
.get('byid')
.get(eventIndex.id)
.decrypt()
)

if (item === undefined) {
const receipt = await this.wallet.getReceiptWithLogs(eventIndex.id).catch(e => {
logger.warn('no receipt found for id:', eventIndex.id, e.message, e)
return undefined
})

if (receipt) {
item = await this.handleReceiptUpdated(receipt)
}
}

return item
})
)
}

Expand All @@ -1240,12 +1254,16 @@ export class UserStorage {
feedItem =>
feedItem.data && ['deleted', 'cancelled'].includes(feedItem.status || feedItem.otplStatus) === false
)
.map(feedItem =>
this.formatEvent(feedItem).catch(e => {
.map(feedItem => {
if (!(feedItem.data && feedItem.data.receiptData)) {
return this.getFormatedEventById(feedItem.id)
}

return this.formatEvent(feedItem).catch(e => {
logger.error('getFormattedEvents Failed formatting event:', e.message, e, feedItem)
return {}
})
)
})
)
}

Expand Down Expand Up @@ -1480,6 +1498,10 @@ export class UserStorage {
_extractDisplayType(type, withdrawStatus, status) {
let sufix = ''

if (type === EVENT_TYPE_WITHDRAW) {
sufix = withdrawStatus
}

if (type === EVENT_TYPE_SEND) {
sufix = withdrawStatus
}
Expand Down Expand Up @@ -1518,7 +1540,9 @@ export class UserStorage {

return (
(type === EVENT_TYPE_BONUS && favicon) ||
(type === EVENT_TYPE_SEND && withdrawStatus === 'error' && favicon) || //errored send
(((type === EVENT_TYPE_SEND && withdrawStatus === 'error') ||
(type === EVENT_TYPE_WITHDRAW && withdrawStatus === 'error')) &&
favicon) || // errored send/withdraw
(await profileFromGun()) || // extract avatar from profile
(type === EVENT_TYPE_CLAIM || address === '0x0000000000000000000000000000000000000000' ? favicon : undefined)
)
Expand Down Expand Up @@ -1568,7 +1592,7 @@ export class UserStorage {
.get('queue')
.get(event.id)
.putAck(event)
this.updateFeedEvent(event)
await this.updateFeedEvent(event)
logger.debug('enqueueTX ok:', { event, putRes })
return true
} catch (e) {
Expand Down Expand Up @@ -1617,7 +1641,9 @@ export class UserStorage {
*/
async updateEventStatus(eventId: string, status: string): Promise<FeedEvent> {
const feedEvent = await this.getFeedItemByTransactionHash(eventId)

feedEvent.status = status

return this.feed
.get('byid')
.get(eventId)
Expand All @@ -1631,30 +1657,46 @@ export class UserStorage {
}

/**
* Sets the event's otpl status
* Sets the event's status
* @param {string} eventId
* @param {string} status
* @returns {Promise<FeedEvent>}
*/
async updateEventOtplStatus(eventId: string, status: string): Promise<FeedEvent> {
async updateOTPLEventStatus(eventId: string, status: string): Promise<FeedEvent> {
const feedEvent = await this.getFeedItemByTransactionHash(eventId)
feedEvent.status = status

if (feedEvent.data) {
feedEvent.data.otplStatus = status
}
feedEvent.otplStatus = status

return this.updateFeedEvent(feedEvent)
return this.feed
.get('byid')
.get(eventId)
.secretAck(feedEvent)
.then()
.then(_ => feedEvent)
.catch(e => {
logger.error('updateOTPLEventStatus failedEncrypt byId:', e.message, e, feedEvent)
return {}
})
}

/**
* Sets the event's status as error
* @param {*} err
* @returns {Promise<FeedEvent>}
* @param {string} txHash
* @returns {Promise<void>}
*/
async markWithErrorEvent(err: any): Promise<FeedEvent> {
const error = JSON.parse(`{${err.message.split('{')[1]}`)
await this.updateEventOtplStatus(error.transactionHash, 'error')
async markWithErrorEvent(txHash: string): Promise<void> {
if (txHash === undefined) {
return
}
const release = await this.feedMutex.lock()

try {
await this.updateEventStatus(txHash, 'error')
} catch (e) {
logger.error('Failed to set error status for feed event', e.message, e)
} finally {
release()
}
}

/**
Expand All @@ -1681,7 +1723,7 @@ export class UserStorage {
* @returns {Promise<FeedEvent>}
*/
async cancelOTPLEvent(eventId: string): Promise<FeedEvent> {
await this.updateEventOtplStatus(eventId, 'cancelled')
await this.updateOTPLEventStatus(eventId, 'cancelled')
}

/**
Expand Down
13 changes: 11 additions & 2 deletions src/lib/undux/utils/withdraw.js
Expand Up @@ -23,16 +23,25 @@ type ReceiptType = {
*
* @param {Store} store - Undux store
* @param {string} code - code that unlocks the escrowed payment
* @param {string} reason - the reason of payment
* @returns {Promise} Returns the receipt of the transaction
*/
export const executeWithdraw = async (store: Store, code: string, reason: string): Promise<ReceiptType> => {
export const executeWithdraw = async (
store: Store,
code: string,
reason: string
): Promise<ReceiptType | { status: boolean }> => {
log.info('executeWithdraw', code, reason)
try {
const { amount, sender, status } = await goodWallet.getWithdrawDetails(code)
if (status === WITHDRAW_STATUS_PENDING) {
let txHash

return new Promise((res, rej) => {
goodWallet.withdraw(code, {
onTransactionHash: transactionHash => {
txHash = transactionHash

const transactionEvent: TransactionEvent = {
id: transactionHash,
date: new Date().toString(),
Expand All @@ -48,7 +57,7 @@ export const executeWithdraw = async (store: Store, code: string, reason: string
res({ status, transactionHash })
},
onError: e => {
userStorage.markWithErrorEvent(e)
userStorage.markWithErrorEvent(txHash)
rej(e)
},
})
Expand Down

0 comments on commit 68e345c

Please sign in to comment.