From 68e345cfb96a81b4a5faac42232f7b6266d25f6c Mon Sep 17 00:00:00 2001 From: sirpy Date: Sun, 1 Dec 2019 15:09:09 +0200 Subject: [PATCH] #955 withdraw transaction failed (#1013) * 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 --- src/components/dashboard/Claim.js | 9 +- .../FeedItems/EventSettingsByType.js | 15 ++++ .../dashboard/FeedItems/FeedModalItem.js | 2 +- .../dashboard/FeedItems/ListEventItem.js | 6 +- src/components/dashboard/FeedList.js | 2 +- src/components/dashboard/SendLinkSummary.js | 11 ++- src/components/dashboard/SendQRSummary.js | 9 +- src/lib/gundb/UserStorageClass.js | 86 ++++++++++++++----- src/lib/undux/utils/withdraw.js | 13 ++- 9 files changed, 117 insertions(+), 36 deletions(-) diff --git a/src/components/dashboard/Claim.js b/src/components/dashboard/Claim.js index d462392173..1c3f7e4e94 100644 --- a/src/components/dashboard/Claim.js +++ b/src/components/dashboard/Claim.js @@ -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, @@ -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) { diff --git a/src/components/dashboard/FeedItems/EventSettingsByType.js b/src/components/dashboard/FeedItems/EventSettingsByType.js index fa9b436aed..dcbda70f20 100644 --- a/src/components/dashboard/FeedItems/EventSettingsByType.js +++ b/src/components/dashboard/FeedItems/EventSettingsByType.js @@ -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', diff --git a/src/components/dashboard/FeedItems/FeedModalItem.js b/src/components/dashboard/FeedItems/FeedModalItem.js index 4ba29e1ac6..aec707410e 100644 --- a/src/components/dashboard/FeedItems/FeedModalItem.js +++ b/src/components/dashboard/FeedItems/FeedModalItem.js @@ -42,7 +42,7 @@ const FeedModalItem = (props: FeedEventProps) => { > {item.type === 'feedback' ? ( - ) : itemType === 'senderror' ? ( + ) : ['senderror', 'withdrawerror'].includes(itemType) ? ( ) : ( diff --git a/src/components/dashboard/FeedItems/ListEventItem.js b/src/components/dashboard/FeedItems/ListEventItem.js index dfe003dfb3..540ba178b6 100644 --- a/src/components/dashboard/FeedItems/ListEventItem.js +++ b/src/components/dashboard/FeedItems/ListEventItem.js @@ -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 @@ -64,7 +65,7 @@ const ListEvent = ({ item: feed, theme, styles }: FeedEventProps) => { source={feed.data && feed.data.endpoint && feed.data.endpoint.avatar} /> - {itemType === 'senderror' ? ( + {isErrorCard ? ( <> {`We're sorry.`} @@ -107,7 +108,7 @@ const getWelcomeStyles = ({ theme }) => ({ display: 'inline', }, welcomeText: { - flexShrink: 0, + textAlign: 'left', }, }) @@ -248,6 +249,7 @@ const getStylesFromProps = ({ theme }) => ({ }, mainText: { textAlignVertical: 'middle', + paddingTop: 5, }, }) diff --git a/src/components/dashboard/FeedList.js b/src/components/dashboard/FeedList.js index fd20246477..f1ac36efd0 100644 --- a/src/components/dashboard/FeedList.js +++ b/src/components/dashboard/FeedList.js @@ -110,7 +110,7 @@ const FeedList = ({ }) .catch(e => { showErrorDialog('Canceling the payment link has failed', e) - userStorage.recoverEvent(id) + userStorage.updateOTPLEventStatus(id, 'pending') }) } } diff --git a/src/components/dashboard/SendLinkSummary.js b/src/components/dashboard/SendLinkSummary.js index 90ae535766..30924747f4 100644 --- a/src/components/dashboard/SendLinkSummary.js +++ b/src/components/dashboard/SendLinkSummary.js @@ -109,6 +109,8 @@ const SendLinkSummary = ({ screenProps }: AmountProps) => { */ const generateLink = () => { try { + let txHash + // Generate link deposit const generateLinkResponse = goodWallet.generateLink( amount, @@ -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) { diff --git a/src/components/dashboard/SendQRSummary.js b/src/components/dashboard/SendQRSummary.js index f063f9f40d..f8c54e8e7b 100644 --- a/src/components/dashboard/SendQRSummary.js +++ b/src/components/dashboard/SendQRSummary.js @@ -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 = { @@ -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) { diff --git a/src/lib/gundb/UserStorageClass.js b/src/lib/gundb/UserStorageClass.js index 13ecfbc03f..4e21138040 100644 --- a/src/lib/gundb/UserStorageClass.js +++ b/src/lib/gundb/UserStorageClass.js @@ -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' @@ -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, @@ -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 + }) ) } @@ -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 {} }) - ) + }) ) } @@ -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 } @@ -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) ) @@ -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) { @@ -1617,7 +1641,9 @@ export class UserStorage { */ async updateEventStatus(eventId: string, status: string): Promise { const feedEvent = await this.getFeedItemByTransactionHash(eventId) + feedEvent.status = status + return this.feed .get('byid') .get(eventId) @@ -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} */ - async updateEventOtplStatus(eventId: string, status: string): Promise { + async updateOTPLEventStatus(eventId: string, status: string): Promise { 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} + * @param {string} txHash + * @returns {Promise} */ - async markWithErrorEvent(err: any): Promise { - const error = JSON.parse(`{${err.message.split('{')[1]}`) - await this.updateEventOtplStatus(error.transactionHash, 'error') + async markWithErrorEvent(txHash: string): Promise { + 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() + } } /** @@ -1681,7 +1723,7 @@ export class UserStorage { * @returns {Promise} */ async cancelOTPLEvent(eventId: string): Promise { - await this.updateEventOtplStatus(eventId, 'cancelled') + await this.updateOTPLEventStatus(eventId, 'cancelled') } /** diff --git a/src/lib/undux/utils/withdraw.js b/src/lib/undux/utils/withdraw.js index 3459bd6e5c..55ba447a35 100644 --- a/src/lib/undux/utils/withdraw.js +++ b/src/lib/undux/utils/withdraw.js @@ -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 => { +export const executeWithdraw = async ( + store: Store, + code: string, + reason: string +): Promise => { 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(), @@ -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) }, })