The Installment Agent specializes in the automated processing of customer requests for installment payment adjustments. It analyzes, validates, and processes modification requests for monthly payments and ensures a seamless implementation in the billing system. Intelligent checking mechanisms allow for installment changes within sensible parameters, while simultaneously ensuring payment security.

Core functionalities

The Installment Agent handles the entire process of installment adjustment, offering the following key services:

  • Needs Analysis: Assessment of the desired change in the context of actual consumption
  • Plausibility Check: Automatic validation of whether the requested change lies within reasonable limits
  • Implementation: Direct adjustment of installment amounts in all relevant systems
  • Communication: Automated confirmation of the change, indicating the effective date

Process Flow

The agent follows a structured process that ensures both customer satisfaction and financial stability:

  1. Recognition of concern and extraction of relevant information (desired installment amount, point in time)
  2. Comparison with consumption data to verify the appropriateness of the requested change
  3. Evaluation of change request based on predefined rules and limits
  4. Execution of the adjustment upon positive evaluation or suggestion of alternative amounts
  5. Transparent communication with the customer about the outcome and next steps

By combining data analysis and clear decision rules, the Installment Agent enables speedy, consistent, and customer-oriented handling of change requests while simultaneously relieving the customer service team.

<?php

use EnneoSDK\Api;
use EnneoSDK\IntentInfo;
use EnneoSDK\IntentOption;
use EnneoSDK\Interaction;

require(getenv()['SDK'] ?? 'sdk.php');
/** @var stdClass $in */

######### Expected Input:
// contractId -> ID of the contract (required)
// requestedDeposit -> Amount of the deposit (required)
// channel -> ticket channel. Voice and chat can be handled synchronously (required)

######### Possible Actions:
// For better readability, actions can be prefixed with "base_", if they are handled in main agent business logic
// Increase requested deposit to minimum allowed
const BASE_INCREASE_DEPOSIT = 'base_increase_deposit';
// Decrease requested deposit to maximum allowed
const BASE_DECREASE_DEPOSIT = 'base_decrease_deposit';
// Deposit is not valid. Inform customer.
const DEPOSIT_INVALID = 'deposit_invalid';
// Save valid deposit
const SAVE_DEPOSIT = 'save_deposit';


$interaction = new Interaction($in);
$deposit = getCurrentDeposit($in->contractId);


######### ACTION: Deposit already validated, "save deposit" action selected
if ($in->_action === SAVE_DEPOSIT) {
    $params = [
        'contractId' => $in->contractId,
        'depositValue' => $in->requestedDeposit,
    ];
    try {
        saveDeposit($in->contractId, $in->requestedDeposit);
        stopProcessing($interaction);
    } catch (Throwable $t) {
        $interaction->infos[] = new IntentInfo(
            type: 'danger',
            message: sprintf('API-Aufruf fehlgeschlagen: %s', $t->getMessage())
        );
        // add the "try again" option using an empty action type.
        $interaction->options[] = new IntentOption(
            type: '',
            name: 'Erneut versuchen',
            recommended: true
        );
    }
}


######### STEP: Fetch current deposit value
$currentDepositValue = getCurrentDeposit($in->contractId);
$minAllowed = round($currentDepositValue * 0.95, 2);
$maxAllowed = round($currentDepositValue * 2.5, 2);
$interaction->infos[] = new IntentInfo(
    type: 'neutral',
    message: sprintf('Aktueller Abschlag: %s. Min.: %s  Max.: %s', $currentDepositValue, $minAllowed, $maxAllowed)
);

######### ACTION: Set Deposit to the minimum
if ($in->_action === BASE_INCREASE_DEPOSIT) {
    $interaction->infos[] = new IntentInfo(
        type: 'neutral',
        message: sprintf('Abschlag auf %s hochgesetzt', $currentDepositValue)
    );
    $in->requestedDeposit = $minAllowed;
}

######### ACTION: Set Deposit to the maximum
if ($in->_action === BASE_DECREASE_DEPOSIT) {
    $interaction->infos[] = new IntentInfo(
        type: 'neutral',
        message: sprintf('Abschlag auf %s reduziert', $currentDepositValue)
    );
    $in->requestedDeposit = $maxAllowed;
}

######### STEP: Validate deposit
// this is a sample validation with a tolerance while decreasing and increasing the allowed deposit values
// you can adjust this to your needs
$isDepositValid = true;
if ($in->requestedDeposit < $minAllowed) {
    $interaction->infos[] = new IntentInfo(
        type: 'warning',
        message: sprintf('Gewünschter Abschlag zu niedrig. Mindestabschlag: %s', $minAllowed)
    );
    $interaction->options[] = new IntentOption(
        type: BASE_INCREASE_DEPOSIT,
        name: 'Auf Mindestabschlag erhöhen',
        recommended: isSyncChannel($in)
    );
    $isDepositValid = false;
}
if ($in->requestedDeposit > $maxAllowed) {
    $interaction->infos[] = new IntentInfo(
        type: 'warning',
        message: sprintf('Abschlag zu hoch. Max: %s €', $maxAllowed)
    );
    $interaction->options[] = new IntentOption(
        type: BASE_DECREASE_DEPOSIT,
        name: 'Auf Maximalabschlag reduzieren',
        recommended: isSyncChannel($in)
    );
    $isDepositValid = false;
}
// If asking the customer for an adjusted deposit amount is not possible (email / letter etc),
// recommended action would be to stop processing and let the customer know that the deposit can't be adjusted
if (!$isDepositValid && !isSyncChannel($in)) {
    $interaction->options[] = new IntentOption(
        type: DEPOSIT_INVALID,
        name: 'Kunden informieren',
        recommended: true
    );
}
if (!$isDepositValid) {
    stopProcessing($interaction);
}

######### STEP: Add Action to save deposit in the system
$interaction->infos[] = new IntentInfo(
    type: 'success',
    message: 'Angefragter Abschlag nicht valid'
);
$interaction->options[] = new IntentOption(
    type: SAVE_DEPOSIT,
    name: 'Abschlag speichern',
    recommended: true
);
stopProcessing($interaction);


######### HELPER FUNCTIONS #########

/**
 * Function simulating a request to an external API and returning the current deposit value
 */
function getCurrentDeposit(int $contractId): float
{
    $response = Api::call(
        method: 'POST',
        url: 'https://echo.enneo.ai',
        params: ['value' => 123.45]
    );

    return $response->vlaue ?? 123.45;
}

function saveDeposit(int $contractId, float $requestedDeposit): void
{
    $response = Api::call(
        method: 'POST',
        url: 'https://echo.enneo.ai',
        params: [
            'contractId' => $contractId,
            'depositValue' => $requestedDeposit,
        ]
    );
}

function isSyncChannel(stdClass $in): bool
{
    return in_array($in->channel, ['chat', 'phone']);
}

/**
 * Business logic execution should always return an Interaction object in JSON format
 */
function stopProcessing(Interaction $interaction): void
{
    echo json_encode($interaction);
    exit();
}