Search code examples
restarchitecturesingle-page-applicationsoftware-designwebapi

SPA - confirmation for an action based on server side validation


I am looking for a reusable solution for a problem where some actions performed by a user may need further confirmation from the user before completion.

I am developing an app using React frontend and WebAPI backend.

Lets say user is initiating fund transfer and assume we cannot check the available fund in his/her account client side before initiating the transfer. When user submits the request (via HTTP POST), the request should complete as normal if the user's account has enough fund whereas if the fund is not enough and the user is within overdraft limits then user should be prompted to use overdraft and only when the user confirms to use overdraft the request should complete.

Another scenario is, say user wants to delete one of the payee account (via HTTP DELETE). Delete should finish normal when there is no recurring payment setup to payee account. User should be prompted further when there is a recurring payment setup (checking the recurring account can only be done on server side and it will be done only when needed (so doing this in advance and feeding to client side is not an option).

I am sure this is a common scenario in any application but can't find reasonable solution to this problem in the internet.

thanks in advance


Solution

  • It's not so much that the scenario is not common - you put a weird requirement on it. In an ideal design - you'd ask the customer to turn-on the overdraft capability, which would be a separate API call to a api/settings endpoint perhaps. You are combining that with the regular request payload, which makes things slightly more complex.

    You just need a detailed enough response contract and a front-end that handles those cases, that's about it.

    I've mocked one below.

    Here's the description of what you have below for transferring the funds case. You could modify some of these response contracts to be a lot more descriptive so you can handle a lot more cases.

    1. Initiate transfer request from the client (react-app) to server. Pass transfer details. Note that you are marking it as DirectTry.
    2. DirectTry fails due to account funds issue / but you validated that you can go overdraft. We add a guid and the transaction info, customer info to a dictionary, just in case if the customer wants to validate the overdraft. You probably should have expiry here as well.
    3. Return a prompt state to react-app. You could extend this with a message being passed from the server to client We need you to approve going into overdraft for this transaction / what's the follow-up state etc...We also transfer a guid, just to make sure it is in fact the same device, same transaction request that's going to enable the overdraft.
    4. On react-app side, you see the prompt state, boot-up a notification component saying click here to approve your overdraft.
    5. You click approve and the same guid is passed to the server side with a different property bag, indicating that this is now also allowing overdrafts.
    6. Transaction goes through.
    
    public enum State
    {
       // everything worked
       Done,
    
       // error - no retry needed.
       CriticalError,
    
       // additional prompt
       Prompt,
    }
    
    public class ServiceResult
    {
       // some state.
       public State State { get; set; }
    
       ... some other props...
    }
    
    
    
    /// Part of some api controller
    [Route("api/funds")]
    [HttpPost]
    public async Task<ServiceResult> TransferFunds(FundTransferPayload payload)
    {
       // ... do some ops.
       ValidationResult validationResult; = new ValidationResult();
       if (payload.OperationType == OperationType.DirectTry)
       {
           validationResult = new ValidationResult();
       }
       // pass the guid we returned to front-end back to service to make sure
       else if (payload.OperationType == OperationType.PromptProvided)
       {
          validationResult = ValidateOp(
               ValidationDictionary[payload.promptGuid],
               payload.customerId,
               payload.operationType,
               payload.operationId);
       }
    
       var transferResult = await SomeService.SomeOps(validationResult);
       if (transferResult.Succeeded) {...}
       else if (transferResult.NeedsOverdraft)
       {
          var someGuid = new Guid();
          ValidationDictionary.Add(new Key(someGuid), new Value(payload.customerId, 'overdraft', transferResult.operationId));
          return new ServiceResult() { State=Prompt, ...otherprops... };
       }
       else if (transferResult.Failed) {...}
    }
    
    

    and in your front-end something like this...

    
    axios.post('/api/funds', {
        operationType: 'DirectTry',
        ...some other props...
      })
      .then(function (response) {
        if (response.data.state === 'Prompt') {
          // save the guid or whatever token you're using - you'll call your API again with that.
          // change the UX with the prompt. Upon prompt, fire the request appropriately.
        }
      });