Cancellation of a consultation appointment with reason for cancellation (Java)

In this use case, the user or the healthcare service provider (doctor, clinic, hospital etc) cancels an appointment from either end

Cancel

Code snippets

Client calls the BAP server to trigger cancel:

    @PostMapping("/healthcare_consultation/cancel_appointment")
    public ResponseEntity cancelAppointment(
            @RequestHeader HttpHeaders headers,
            @RequestBody ClientOrderRequest request) {
        var response = bapApplicationService.cancelOrder(request, headers);
        return ResponseEntity.ok(response);
    }

BAP server generates the protocol request body


/*
Example Request JSON:
{
    "context": {
        "domain": "nic2004:85121",
        "country": "IND",
        "city": "std:080",
        "action": "cancel",
        "core_version": "0.9.2",
        "bap_id": "https://mock_bap.com/",
        "bap_uri": "https://mock_bap.com/beckn/",
        "transaction_id": "1239890342",
        "message_id": "123793824",
        "timestamp": "2021-03-23T10:00:40.065Z"
    },
    "message": {
        "order_id": "order_1",
        "cancellation_reason_id": "reason_3"
    }
}
*/

    public Response cancelOrder(ClientOrderRequest request, HttpHeaders headers) {

        // Generate message id
        var messageId = UUID.randomUUID().toString();

        // Check if transaction id exists in the request.
        // Generate if not exists
        var txnId = StringUtils.hasText(request.getTransactionId())
                ? request.getTransactionId()
                : UUID.randomUUID().toString();

        // Construct the Context based on the request parameters
        var context = Context.builder().domain(request.getDomain())
                .action(Context.ActionEnum.cancel)
                .messageId(messageId)
                .transactionId(txnId)
                .transactionId(UUID.randomUUID().toString())
                .timestamp(new Date().toString())
                .build();

        // Construct the protocol specific object to be passed to the BPP
        // to cancel the order of given appointment id
        // The valid cancellation reasons will be fetched by the BPP's /get_cancellation_reasons endpoint
        var cancelRequest = CancelRequest.builder()
                .context(context)
                .message(CancelMessage.builder()
                        .orderId(request.getOrderId())
                        .cancellationReasonId(request.getCancellationReason())
                        .build())
                .build();

        return invokeCancel(cancelRequest, headers);
    }
}

BAP server calls protocol cancel to the network

    public Response invokeCancel(CancelRequest request, HttpHeaders headers) {

        // Call to look up function which returns the the public key and BPP Endpoint to be called
        var url = lookUp(headers);

        // Call BPP Cancel api from the returned endpoint.
        // Construct request headers with the public key
        var responseEntity = apiClient.post(url[0] + Context.ActionEnum.cancel,
                constructRequestHeaders(),
                request,
                Response.class);

        // Validate the received response
        if (responseEntity.getBody() == null || responseEntity.getBody().getError() != null ||
                "NACK".equals(responseEntity.getBody().getMessage().getAck().getStatus())) {
            // Return custom error to the client
            return null;
        }
        return Response.of("ACK", null);
    }

BAP receives protocol on_cancel

/*
Example Response JSON:
{
    "context": {
        "domain": "nic2004:85121",
        "country": "IND",
        "city": "std:080",
        "action": "on_status",
        "core_version": "0.9.2",
        "bap_id": "https://mock_bap.com/",
        "bap_uri": "https://mock_bap.com/beckn/",
        "transaction_id": "1239890342",
        "message_id": "123793824",
        "timestamp": "2021-03-23T10:00:40.065Z"
    },
    "message": {
        "order": {
            "id": "order_1",
            "state": "Cancelled",
            "items": [
                {
                    "id": "dr_veena_slot_1",
                    "quantity": {
                        "count": 1
                    }
                }
            ],
            "billing": {
                "name": "John Doe",
                "address": {
                    "door": "21A",
                    "name": "ABC Apartments",
                    "locality": "HSR Layout",
                    "city": "Bengaluru",
                    "state": "Karnataka",
                    "country": "India",
                    "area_code": "560102"
                },
                "email": "[email protected]",
                "phone": "+919876543210"
            },
            "fulfillment": {
                "type": "IN-PERSON",
                "end": {
                    "location": {
                        "id": "good-health-koramangala",
                        "gps": "12.9349377,77.6055586"
                    },
                    "time": {
                        "range": {
                            "start": "2021-08-11T04:30:00.000Z",
                            "end": "2021-08-11T05:30:00.000Z"
                        }
                    },
                    "contact": {
                        "phone": "+919876543210",
                        "email": "[email protected]"
                    }
                },
                "agent": {
                    "name": "Dr. Veena H",
                    "dob": "1989-08-12",
                    "gender": "Female",
                    "tags": {
                        "doctor_registration_no": "12345"
                    },
                    "phone": "+919876543210",
                    "email": "[email protected]"
                },
                "customer": {
                    "person": {
                        "name": "Remya C",
                        "dob": "1989-08-12",
                        "gender": "Female"
                    },
                    "contact": {
                        "phone": "+919898987676",
                        "email": "[email protected]"
                    }
                }
            },
            "quote": {
                "price": {
                    "currency": "INR",
                    "value": "1100"
                },
                "breakup": [
                    {
                        "title": "Consultation Charges",
                        "price": {
                            "currency": "INR",
                            "value": "1000"
                        }
                    },
                    {
                        "title": "Service Charge",
                        "price": {
                            "currency": "INR",
                            "value": "100"
                        }
                    }
                ],
                "ttl": "P1H"
            },
            "payment": {
                "uri": "https://api.bpp.com/pay?amt=$amount&txn_id=ksh87yriuro34iyr3p4&mode=upi&[email protected]",
                "tl_method": "http/get",
                "params": {
                    "transaction_id": "ksh87yriuro34iyr3p4",
                    "amount": "1100",
                    "mode": "upi",
                    "vpa": "[email protected]"
                },
                "type": "ON-FULFILLMENT",
                "status": "NOT-PAID"
            }
        }
    }
}
*/
    @PostMapping("/bap/on_cancel")
    public ResponseEntity onCancel(
            @RequestHeader HttpHeaders headers,
            @RequestBody OnCancelRequest request) {
        var response = bapCallbackApplicationService.onCancel(request, headers);
        return ResponseEntity.ok(response);
    }

    public Response onCancel(OnCancelRequest request, HttpHeaders headers) {
        // Validate the headers received
        var isHeadersValid = validateHeaders(headers);
        // Construct and return error if the received headers are invalid
        if (!isHeadersValid) return null;

        // Store the data received based on message id for the client to poll
        saveToDB(request);

        return Response.of("ACK", null);
    }

Client polls BAP to get the on_cancel results

    // Endpoint for the client to poll the cancelled order based on the message id
    @GetMapping("/healthcare_consultation/on_cancel_order")
    public ResponseEntity onCancelOrder(
            @PathVariable(ClientRoutes.PARAM_MESSAGE_ID) String messageId,
            @RequestHeader HttpHeaders headers) {
        var data = bapApplicationService.get(messageId);
        return ResponseEntity.ok(data);
    }