无法在Firestore集合中写入批处理文档

时间:2019-10-30 18:48:36

标签: javascript node.js typescript google-cloud-firestore google-cloud-functions

我的功能从移动应用程序接收数据。发出CASHIN请求时,函数postIntouch将Https请求发送给第三方API,然后将当前交易保存为Firecon集合中的未确认交易。同时等待带有事务状态的第三方回调。触发回调后,enter code here函数通过检查用户集合以查找正确的交易并更新文档中的状态,然后将其发送到分类帐来更新用户余额。我的问题是,由于未确认的交易未保存在用户集合中,因此回调无法更新正确的用户余额。任何帮助,将不胜感激!

检查第三方发送的回调。 在集合中找不到交易的任何痕迹

/**
 * Logic to initiate a cash in/out operation
 * 
 * @param data
 * {
 *   ...
 *   provider: 'ORANGE' | 'TELMOB' | 'TELECEL',
 *   operation: 'CASHIN' | 'CASHOUT' | 'AIRTIME',
 *   amount: number,
 *   otp: string,
 *   ...
 * }
 * 
 * @returns code 200 on transaction pending
 *          In case of errors, return message '5-Insufficiant funds' or '8-Amount cannot be negative'
 */
exports.postIntouch = functions.https.onCall(async (
    data:
        {
            provider: _.IntouchProvider,
            operation: _.IntouchOperation,
            amount: number,
            email : string
            otp: string,
            clientTimestamp: string
        },
    context) => {
    if (!context.auth) {
        throw new functions.https.HttpsError('unauthenticated', 'A user must be authenticated')
    }
    const { provider, operation, amount, otp,} = data

    /* PRELIMINARY VALIDATION */
    if (amount <= 0) throw new functions.https.HttpsError('invalid-argument', `${_.Errors.negativeAmount.code}-${_.Errors.negativeAmount.message}`)
    if (_.INTOUCH_SERVICE[provider][operation].length === 0) throw new functions.https.HttpsError('unimplemented', 'Service not available')

    /* FIND USER */
    const user = await _.findUser({ uid: context.auth.uid })
    if (user === undefined) throw new functions.https.HttpsError('not-found', 'Could not find user')

    /* IF CASHOUT OR AIRTIME OR BILL, MAKE SURE USER HAS ENOUGH FUNDS */
    if (operation === _.IntouchOperation.CASHOUT || operation === _.IntouchOperation.AIRTIME || operation === _.IntouchOperation.BILL) {
        if (user.wallet.fcfa < amount) throw new functions.https.HttpsError('failed-precondition',
            `${_.Errors.insufficiantFunds.code}-${_.Errors.insufficiantFunds.message}`
        )
    }

    /* GENERATE TRANSACTION ID */
    const transactionId = _.db.collection('users-beta').doc(user.uid).collection('transactions').doc().id

    /* CALL INTOUCH API */
    let resp
     try {
         resp = await _.initiateIntouchTransaction(amount, user.phoneNumber, `${user.uid}|${transactionId}`, provider, operation, otp)
     } catch (error) {
         console.log(error)
         throw new functions.https.HttpsError('aborted', 'Intouch transaction failed')
     }
    //If transaction failed, stop execution right here
     if (resp.status !== _.IntouchTransactionStatus.PENDING && resp.status !== _.IntouchTransactionStatus.INITIATED) {
         throw new functions.https.HttpsError('aborted', 'Intouch transaction failed')
     }

    console.log('response '+resp)

    /* UPDATE LEDGER AND FIRESTORE */
    const batch = _.db.batch()
    const userRef = _.db.collection('users-beta').doc(user.uid)
    const transactionRef = _.db.collection('users-beta').doc(user.uid).collection('transactions').doc(transactionId)
    console.log('user reference '+userRef)
    console.log('transactionRef '+transactionRef)

    const record: _.TransactionRecord = {
        type: operation === _.IntouchOperation.CASHIN ? _.TransactionType.CASHIN
            : operation === _.IntouchOperation.CASHOUT ? _.TransactionType.CASHOUT
                : operation === _.IntouchOperation.AIRTIME ? _.TransactionType.AIRTIME
                    : _.TransactionType.BILL,
        sender: {
            uid: user.uid,
            handle: '',
            firstName: '',
            lastName:  otp
        },
        receiver: {
            uid: '',
            handle: '',
            firstName: '',
            lastName: ''
        },
        amount: amount,
        passPhrase: '',
        status: _.TransactionStatus.ONHOLD,
        ledgerRecords: { first: {}, second: {} },
        intouchResponses: { first: resp, second: {} },
        clientTimestamp: data.clientTimestamp
    }

    //If CASHOUT OR AIRTIME OR BILL, reserve tokens on ledger
    if (operation === _.IntouchOperation.CASHOUT || operation === _.IntouchOperation.AIRTIME || operation === _.IntouchOperation.BILL) {
        //Ledger
        let transaction
        try {
            transaction = await _.writeTransactionToLedger(_.TransactionType.RESERVATION, amount,
                { sourceAccountId: user.uid, destinationAccountId: operation })
        } catch (error) {
            console.log(error)
            throw new functions.https.HttpsError('internal', 'Could not write transaction to ledger')
        }

        //Firestore
        record.ledgerRecords.first = transaction
        batch
            .update(userRef, { wallet: { fcfa: user.wallet.fcfa - amount, points: user.wallet.points } })
            .set(transactionRef, record)

    } else if (operation === _.IntouchOperation.CASHIN) {
        //Firestore
        batch.set(transactionRef, record)
    }

    /* WRITE TO FIRESTORE */
     try {
         await batch.commit()
     } catch (error) {
         console.log(error)
         throw new functions.https.HttpsError('unknown', 'Firestore write failed after intouch api call')
     }
})

/**
 * Callback for Intouch
 */
exports.intouchCallback = functions.https.onRequest(async (req, res) => {
    const { partner_transaction_id, status } = req.body
    if (typeof partner_transaction_id !== 'string' || typeof status !== 'string') { res.end(); return }
console.log(req.body)
    //Extract both from partnerTransactionId
    const [uid, transactionId] = partner_transaction_id.split('|')

    /* LOOKUP USER AND TRANSACTION */
    const user = await _.findUser({ uid: uid })
    if (user === undefined) { res.end(); return }

    const transaction = await _.findTransaction(transactionId, { senderUid: uid })
    if (transaction === undefined) {
        console.log('cannot find transaction')
        res.end(); 
        return 
    }

    /* UPDATE BALANCE ON LEDGER AND FIRESTORE BASED ON STATUS */
    const batch = _.db.batch()
    const userRef = _.db.collection('users-beta').doc(uid)
    const transactionRef = _.db.collection('users-beta').doc(uid).collection('transactions').doc(transactionId)

    let ledgerRecord
    if (status === _.IntouchTransactionStatus.SUCCESSFUL) {
        if (transaction.type === _.TransactionType.CASHIN) {
            //WRITE TO LEDGER
            try {
                ledgerRecord = await _.writeTransactionToLedger(transaction.type, transaction.amount, { destinationAccountId: uid })
            } catch (error) {
                console.log(error)
                res.end(); 
                return error
            }
            //Update user balance
            batch.update(userRef, { wallet: { fcfa: user.wallet.fcfa + transaction.amount, points: user.wallet.points } })
            //Update transaction record
            batch.update(transactionRef,
                {
                    status: _.IntouchTransactionStatus.SUCCESSFUL,
                    ledgerRecords: { first: ledgerRecord, second: {} },
                    intouchResponses: { first: transaction.intouchResponses.first, second: req.body }
                }
            )
        } else if (transaction.type === _.TransactionType.CASHOUT || transaction.type === _.TransactionType.AIRTIME || transaction.type === _.TransactionType.BILL) {
            //WRITE TO LEDGER
            try {
                ledgerRecord = await _.writeTransactionToLedger(transaction.type, transaction.amount, { sourceAccountId: uid })
            } catch (error) {
                console.log(error)
                res.end(); return
            }

            //Update transaction record
            batch.update(transactionRef,
                {
                    status: _.IntouchTransactionStatus.SUCCESSFUL,
                    ledgerRecords: { first: transaction.ledgerRecords.first, second: ledgerRecord },
                    intouchResponses: { first: transaction.intouchResponses.first, second: req.body }
                }
            )
        }

    } else if (status === _.IntouchTransactionStatus.FAILED) {
        //Update status and save intouch response in transaction record
        batch.update(transactionRef,
            {
                status: _.IntouchTransactionStatus.FAILED,
                intouchResponses: { first: transaction.intouchResponses.first, second: req.body }
            }
        )

        if (transaction.type === _.TransactionType.CASHOUT || transaction.type === _.TransactionType.AIRTIME || transaction.type === _.TransactionType.BILL) {
            //CANCEL TRANSACTION ON LEDGER
            const operation = transaction.type === _.TransactionType.CASHOUT ? _.IntouchOperation.CASHOUT
                : transaction.type === _.TransactionType.AIRTIME ? _.IntouchOperation.AIRTIME
                    : _.IntouchOperation.BILL
            try {
                ledgerRecord = await _.writeTransactionToLedger(_.TransactionType.CANCELLATION, transaction.amount,
                    { sourceAccountId: uid, destinationAccountId: operation })
            } catch (error) {
                console.log(error)
                res.end(); return
            }

            //REVERT BALANCE IN FIRESTORE
            batch
                .update(userRef, { wallet: { fcfa: user.wallet.fcfa + transaction.amount, points: user.wallet.points } })
                .update(transactionRef, { ledgerRecords: { first: transaction.ledgerRecords.first, second: ledgerRecord } })
        }
    } else { res.end(); return }

    //Commit batch
    try {
        await batch.commit()
    } catch (error) {
        console.log(error)
    }

    /* SEND NOTIFICATION TO USER */
    if (status === _.IntouchTransactionStatus.SUCCESSFUL) {
        try {
            switch (transaction.type) {
                case _.TransactionType.CASHIN: {
                    await _.notify(user.notificationToken, `Votre balance a été creditée de ${transaction.amount}`, '', { transactionId: transactionId })
                    break
                }
                case _.TransactionType.CASHOUT: {
                    await _.notify(user.notificationToken, `Votre balance a été debitée de ${transaction.amount}`, '', { transactionId: transactionId })
                    break
                }
                case _.TransactionType.AIRTIME: {
                    await _.notify(user.notificationToken, `Votre achat de ${transaction.amount} d'unités a réussi`, '', { transactionId: transactionId })
                    break
                }
                /* case _.TransactionType.BILL: {
                    
                } */
                default: {
                    break
                }
            }
        } catch (error) {
            console.log(error)
        }
    } else if (status === _.IntouchTransactionStatus.FAILED) {
        try {
            await _.notify(user.notificationToken, `Votre transaction ${transactionId} a echouée`, '', { transactionId: transactionId })
        } catch (error) {
            console.log(error)
        }
    }
    res.end(); return
})

1 个答案:

答案 0 :(得分:0)

我无法彻底检查代码,但是您期望

 const transactionId = _.db.collection('users-beta').doc(user.uid).collection('transactions').doc().id

将数据添加到Firestore。如果是这样,则不会。

您需要执行.add或.set来执行此操作。