async function createTransaction()

in quests/develop-apis-apigee/grpc-backend/server.js [488:614]


async function createTransaction(call, callback) {
	const customerEmail = call.request.customerEmail;
	const accountName = call.request.accountName;
	const transactionType = call.request.transactionType;
	const amount = call.request.amount;
	const toCustomerEmail = call.request.toCustomerEmail;
	const toAccountName = call.request.toAccountName;
	if (!customerEmail) {
		return callback(invalidArg('customerEmail missing'));
	}
	if (!accountName) {
		return callback(invalidArg('accountName missing'));
	}
	if (!isValidTransactionType(transactionType)) {
		return callback(invalidArg(`transactionType must be ${DEPOSIT}, ${WITHDRAWAL}, or ${TRANSFER}.`));
	}
	if (!isValidCurrency(amount) || amount <= 0) {
		return callback(invalidArg(`amount must be positive with a maximum of 2 digits after the decimal point`));
	}
	if (transactionType === TRANSFER && !toCustomerEmail) {
		return callback(invalidArg(`toCustomerEmail missing, required for TRANSFER transactions`));
	}
	if (transactionType === TRANSFER && !toAccountName) {
		return callback(invalidArg(`toAccountName missing, required for TRANSFER transactions`));
	}
	if (transactionType !== TRANSFER && toCustomerEmail.length > 0) {
		return callback(invalidArg(`toCustomerEmail specified, but only allowed for TRANSFER transactions`));
	}
	if (transactionType !== TRANSFER && toAccountName.length > 0) {
		return callback(invalidArg(`toAccountName specified, but only allowed for TRANSFER transactions`));
	}
	const transactionDocRef = db.collection('transactions').doc();
	const transactionDocId = transactionDocRef.id;
	const customerAccountPath = `customers/${customerEmail}/accounts/${accountName}`
	const transactionData = buildTransaction(call.request);
	transactionData.id = transactionDocId;

    console.log(`Creating initial transaction ${transactionDocId}: transactionData=${JSON.stringify(transactionData)}`);
    await transactionDocRef.create(transactionData).then((result) => {
        // success
    }).catch((err) => {
		const errMessage = `transaction ${transactionDocId}: transaction creation failure`;
		return callback(unknown(errMessage));
	});

    console.log(`Starting firestore transaction`);
    try {
        await db.runTransaction(async (t) => {
            // check account
            const accountRef = db.doc(customerAccountPath);
            console.log(`getting account ${customerAccountPath}`);
            const accountDoc = await t.get(accountRef);
            if (!accountDoc.exists) {
                console.log(`account ${customerAccountPath} does not exist`);
                // account must exist
				const errMessage = `source account does not exist`;
				t.update(transactionDocRef, { failureMessage: errMessage });
				return callback(notFound(`transaction ${transactionDocId}: ${errMessage}`));
            }
            var accountData = accountDoc.data();
            console.log(`accountData=${JSON.stringify(accountData)}`);
            var accountBalance = accountData.balance;
            var overdraftAllowed = accountData.overdraftAllowed;
            console.log(`accountData: balance=${accountBalance}, overdraftAllowed=${overdraftAllowed}`);

			var toAccountPath = "";
            var toAccountRef = null;
            var toAccountDoc = null;
            var toAccountData = null;
            if (transactionType === TRANSFER) {
                // to account is required
				toAccountPath = `customers/${toCustomerEmail}/accounts/${toAccountName}`
                toAccountRef = db.doc(toAccountPath);
                toAccountDoc = await t.get(toAccountRef);
                if (!toAccountDoc.exists) {
                    // to account must exist
					const errMessage = `destination account does not exist`;
					t.update(transactionDocRef, { failureMessage: errMessage });
					return callback(notFound(`transaction ${transactionDocId}: ${errMessage}`));
                }
                toAccountData = toAccountDoc.data();
            }

            // get transaction doc (GET required for a firestore transaction)
            const transactionDoc = await t.get(transactionDocRef);
            var transactionDocData = transactionDoc.data();

            var balanceChange;
            if (transactionType === TRANSFER || transactionType === WITHDRAWAL) {
                balanceChange = -amount;

                // check for overdraft
                if (!overdraftAllowed && amount > accountBalance) {
                    // would be overdraft
					const errMessage = `insufficient funds`;
					t.update(transactionDocRef, { failureMessage: errMessage });
					return callback(cancelled(`transaction ${transactionDocId}: ${errMessage}`));
                }

                t.update(accountRef, {
                    balance: admin.firestore.FieldValue.increment(balanceChange)
                });
                if (transactionType === TRANSFER) {
                    t.update(toAccountRef, {
                        balance: admin.firestore.FieldValue.increment(-balanceChange)
                    });
                }
            } else { // DEPOSIT
                balanceChange = amount;
                console.log(`depositing ${amount} into account ${account}`);

                t.update(accountRef, {
                    balance: admin.firestore.FieldValue.increment(balanceChange)
                });
                console.log(`deposited ${amount} into account ${account}`);
            }

            transactionDocData.success = true;
            console.log(`updating transaction ${transactionDocId} with success`);
            t.update(transactionDocRef, { success: true, id: transactionDocId });
			return callback(null, transactionDocData);
        });
    } catch (e) {
        // transaction failure
		return callback(unknown('transaction creation failure'));
    }
}