Esempio n. 1
0
        private async Task <FundsResponseResult> DigestResponseItemAsync(
            FundsResponseFile file,
            FundsResponseFileItem item,
            FundsTransferRequest fundsTransferRequest,
            FundsTransferBatchMessage responseBatchMessage,
            SO statefulObject,
            State stateAfterRequest)
        {
            if (file == null)
            {
                throw new ArgumentNullException(nameof(file));
            }
            if (item == null)
            {
                throw new ArgumentNullException(nameof(item));
            }
            if (fundsTransferRequest == null)
            {
                throw new ArgumentNullException(nameof(fundsTransferRequest));
            }

            var line = new FundsResponseLine(file, item, responseBatchMessage.ID);

            return(await DigestResponseLineAsync(fundsTransferRequest, line, statefulObject, stateAfterRequest));
        }
Esempio n. 2
0
        private async Task <FundsResponseResult> DigestResponseLineAsync(
            FundsTransferRequest fundsTransferRequest,
            FundsResponseLine line,
            SO statefulObject,
            State stateAfterRequest)
        {
            if (fundsTransferRequest == null)
            {
                throw new ArgumentNullException(nameof(fundsTransferRequest));
            }
            if (line == null)
            {
                throw new ArgumentNullException(nameof(line));
            }

            if (statefulObject == null || stateAfterRequest == null)
            {
                return(await DigestResponseLineAsync(fundsTransferRequest, line));
            }

            var eventType = GetEventTypeFromResponseLine(line);

            var previousEvent = TryGetExistingDigestedFundsTransferEvent(fundsTransferRequest, eventType, line.ResponseCode);

            if (previousEvent != null)
            {
                return(new FundsResponseResult
                {
                    Event = previousEvent,
                    Line = line,
                    IsAlreadyDigested = true
                });
            }

            var actionArguments = new Dictionary <string, object>
            {
                [StandardArgumentKeys.BillingItem] = line
            };

            try
            {
                var fundsResponseResult = new FundsResponseResult
                {
                    Line = line
                };

                // Attempt to get the next path to be executed. Any exception will be recorded in a funds transfer event with ExceptionData.

                string statePathCodeName = TrySpecifyNextStatePath(statefulObject, stateAfterRequest, line);

                if (statePathCodeName != null)                 // Should a path be executed?
                {
                    var statePath = await statePathsByCodeNameCache.Get(statePathCodeName);

                    using (var transaction = this.DomainContainer.BeginTransaction())
                    {
                        var transition = await ExecuteStatePathAsync(
                            statefulObject,
                            statePath,
                            actionArguments);

                        fundsResponseResult.Event = transition.FundsTransferEvent;

                        R remittance = null;

                        if (transition.FundsTransferEventID.HasValue)
                        {
                            remittance = await this.DomainContainer.Remittances.SingleOrDefaultAsync(r => r.FundsTransferEventID == transition.FundsTransferEventID);
                        }

                        if (transition.FundsTransferEvent != null)
                        {
                            await OnResponseLineDigestionSuccessAsync(line, transition.FundsTransferEvent, remittance);
                        }

                        await transaction.CommitAsync();
                    }
                }
                else                 // If no path is specified, record the event directly.
                {
                    using (var accountingSession = CreateAccountingSession())
                        using (GetElevatedAccessScope())
                            using (var transaction = this.DomainContainer.BeginTransaction())
                            {
                                var directActionResult = await accountingSession.AddFundsTransferEventAsync(
                                    fundsTransferRequest,
                                    line.Time,
                                    eventType,
                                    j => AppendResponseJournalAsync(j, fundsTransferRequest, line, eventType, null),
                                    line.BatchMessageID,
                                    line.ResponseCode,
                                    line.TraceCode,
                                    line.Comments);

                                fundsResponseResult.Event = directActionResult.FundsTransferEvent;

                                var remittance = TryGetTransferRemittance(directActionResult);

                                await OnResponseLineDigestionSuccessAsync(line, directActionResult.FundsTransferEvent, remittance);

                                await transaction.CommitAsync();
                            }
                }

                return(fundsResponseResult);
            }
            catch (Exception exception)
            {
                this.DomainContainer.ChangeTracker.UndoChanges();                 // Undo attempted entities.

                return(await RecordDigestionExceptionEventAsync(fundsTransferRequest, line, exception, GetEventTypeFromResponseLine(line)));
            }
        }
Esempio n. 3
0
        /// <summary>
        /// Digestion of a manual line in a batch.
        /// </summary>
        /// <param name="line">The line to accept.</param>
        /// <returns>
        /// Returns the collection of the results which correspond to the
        /// funds transfer requests grouped in the line or an empty collection if the file is not relevant to this manager.
        /// </returns>
        protected internal override async Task <IReadOnlyCollection <FundsResponseResult> > DigestResponseLineAsync(FundsResponseLine line)
        {
            if (line == null)
            {
                throw new ArgumentNullException(nameof(line));
            }

            var associationsQuery = from a in this.FundsTransferEventAssociations
                                    where a.Event.Type == FundsTransferEventType.Pending
                                    let ftr = a.Event.Request
                                              where ftr.GroupID == line.LineID && ftr.BatchID == line.BatchID
                                              select new
            {
                Request = ftr,
                ftr.Events,
                a.StateHolder,
                a.CurrentState,
                StateAfterRequest = a.StateTransition != null ? a.StateTransition.Path.NextState : null
            };

            var associations = await associationsQuery.ToArrayAsync();

            if (associations.Length == 0)
            {
                return(emptyFundsResponseResults);
            }

            var responseResults = new List <FundsResponseResult>(associations.Length);

            foreach (var association in associations)
            {
                var statefulObject = GetStatefulObject(association.StateHolder);

                var fundsResponseResult =
                    await DigestResponseLineAsync(
                        association.Request,
                        line,
                        statefulObject,
                        association.StateAfterRequest);

                responseResults.Add(fundsResponseResult);
            }

            return(responseResults);
        }
Esempio n. 4
0
 /// <summary>
 /// Decides the state path to execute on a stateful object
 /// when a <see cref="FundsResponseLine"/> arrives for it.
 /// Returns null to indicate that no path should be executed and the that the line should
 /// be consumed directly. Throws an exception to abort normal digestion of the line
 /// and to record a transfer event with <see cref="FundsTransferEvent.ExceptionData"/> set instead.
 /// </summary>
 /// <param name="statefulObject">The stateful object for which to decide the state path.</param>
 /// <param name="stateAfterFundsTransferRequest">The state of the <paramref name="statefulObject"/> right after the funds transfer request.</param>
 /// <param name="fundsResponseLine">The batch line arriving for the stateful object.</param>
 /// <returns>Returns the code name of the path to execute or null to execute none.</returns>
 /// <exception cref="Exception">
 /// Thrown to record a funds transfer event with its <see cref="FundsTransferEvent.ExceptionData"/>
 /// containing the thrown exception.
 /// </exception>
 protected abstract string TrySpecifyNextStatePath(
     SO statefulObject,
     State stateAfterFundsTransferRequest,
     FundsResponseLine fundsResponseLine);
Esempio n. 5
0
        /// <summary>
        /// Consumes a <see cref="FundsResponseLine"/> as a billing item by calling
        /// <see cref="AccountingSession{U, BST, P, R, J, D}.AddFundsTransferEventAsync(FundsTransferRequest, DateTime, FundsTransferEventType, Func{J, Task}, long?, string, string, string, Exception)"/>
        /// and, when the <see cref="FundsResponseLine.Status"/> is <see cref="FundsResponseStatus.Succeeded"/>,
        /// appending to the resulting journal by calling <see cref="AppendToJournalAsync(D, SO, J, FundsResponseLine, U)"/>.
        /// </summary>
        /// <param name="accountingSession">
        /// The accounting session, as created
        /// via <see cref="AccountingAction{U, BST, P, R, J, D, S, ST, SO, AS, B}.CreateAccountingSession(D, U)"/>.
        /// </param>
        /// <param name="stateful">The stateful object for which the workflow action runs.</param>
        /// <param name="stateTransition">The state transition being produced.</param>
        /// <param name="billingItem">The <see cref="FundsResponseLine"/>.</param>
        /// <returns></returns>
        protected override async Task <AccountingSession <U, BST, P, R, J, D> .ActionResult> ExecuteAccountingAsync(
            AS accountingSession,
            SO stateful,
            ST stateTransition,
            FundsResponseLine billingItem)
        {
            if (accountingSession == null)
            {
                throw new ArgumentNullException(nameof(accountingSession));
            }
            if (stateful == null)
            {
                throw new ArgumentNullException(nameof(stateful));
            }
            if (billingItem == null)
            {
                throw new ArgumentNullException(nameof(billingItem));
            }

            D domainContainer = accountingSession.DomainContainer;

            var fundsTransferRequest = await GetFundsTransferRequestAsync(stateful, stateTransition, billingItem);

            if (fundsTransferRequest == null)
            {
                throw new UserException(FundsTransferResponseActionResources.INVALID_FUNDS_REQUEST);
            }

            FundsTransferEventType eventType;

            switch (billingItem.Status)
            {
            case FundsResponseStatus.Rejected:
                eventType = FundsTransferEventType.Rejected;
                break;

            case FundsResponseStatus.Failed:
                eventType = FundsTransferEventType.Failed;
                break;

            case FundsResponseStatus.Accepted:
                eventType = FundsTransferEventType.Accepted;
                break;

            case FundsResponseStatus.Succeeded:
                eventType = FundsTransferEventType.Succeeded;
                break;

            default:
                throw new LogicException($"Unexpected funds transfer line status: '{billingItem.Status}'.");
            }

            // Local function to enclose arguments and guard event type.
            async Task AppendToJournalFunctionAsync(J journal)
            {
                if (eventType != FundsTransferEventType.Succeeded)
                {
                    return;
                }

                await AppendToJournalAsync(domainContainer, stateful, journal, billingItem, accountingSession.Agent);
            }

            return(await accountingSession.AddFundsTransferEventAsync(
                       fundsTransferRequest,
                       billingItem.Time,
                       eventType,
                       AppendToJournalFunctionAsync,
                       billingItem.BatchMessageID,
                       billingItem.ResponseCode,
                       billingItem.TraceCode,
                       billingItem.Comments));
        }
Esempio n. 6
0
 /// <summary>
 /// Get the funds transfer request which corresponds to this workflow action.
 /// </summary>
 /// <param name="stateful">The stateful object manipulated during the action.</param>
 /// <param name="stateTransition">The state transition being produced.</param>
 /// <param name="fundsResponseLine">The funds response line.</param>
 protected abstract Task <FundsTransferRequest> GetFundsTransferRequestAsync(SO stateful, ST stateTransition, FundsResponseLine fundsResponseLine);
Esempio n. 7
0
 /// <summary>
 /// Override to enroll to the accounting actions any extra journal lines when
 /// the <see cref="FundsResponseLine.Status"/> of the <paramref name="fundsResponseLine"/>
 /// is <see cref="FundsResponseStatus.Succeeded"/>. Default implementation does nothing.
 /// </summary>
 /// <param name="domainContainer">The domain container in use.</param>
 /// <param name="stateful">The stateful object for which the workflow action runs.</param>
 /// <param name="journal">The journal to append to.</param>
 /// <param name="fundsResponseLine">The funds response line being consumed.</param>
 /// <param name="agent">The user agent running the action.</param>
 protected virtual Task AppendToJournalAsync(D domainContainer, SO stateful, J journal, FundsResponseLine fundsResponseLine, U agent)
 => Task.CompletedTask;
        /// <summary>
        /// Digestion of a manual line in a batch by feeding it to all <see cref="FundsTransferManagers"/>
        /// for digestion and combining results.
        /// </summary>
        /// <param name="line">The line to accept.</param>
        /// <returns>
        /// Returns the collection of the results which correspond to the
        /// funds transfer requests grouped in the line.
        /// </returns>
        protected internal override async Task <IReadOnlyCollection <FundsResponseResult> > DigestResponseLineAsync(FundsResponseLine line)
        {
            if (line == null)
            {
                throw new ArgumentNullException(nameof(line));
            }

            var fundsResponseResults = Enumerable.Empty <FundsResponseResult>();

            foreach (var manager in this.FundsTransferManagers)
            {
                var managerResults = await manager.DigestResponseLineAsync(line);

                await manager.PostProcessLinesAsync(line.BatchID, managerResults, line.BatchMessageID);

                fundsResponseResults.Concat(managerResults);
            }

            return(fundsResponseResults.ToArray());
        }