vrijdag 13 september 2013

Betalingskenmerken in procedureel PHP

Een betalingskenmerk is een unieke cijfercode waaraan een betaling is te herkennen. Een betalingskenmerk verhoogt de efficiëntie waarmee betalingen kunnen worden verwerkt en verkleint de kans op verwerkingsfouten. Betalingskenmerken worden gebruikt bij acceptgiro’s, internetbankieren en elektronisch betalen. In deze blogpost leg ik uit hoe je zelf een functie voor het genereren van betalingskenmerken maakt in procedureel PHP.

Opstellen van een PHP-functie

Voor een betalingskenmerk kun je een PHP-functie gebruiken die een getal accepteert en het betalingskenmerk retourneert. Een PHP-functie die je zelf maakt, noemen we in het Engels een custom function of een user-defined function. Een PHP-prototype van zo’n functie ziet er bijvoorbeeld zo uit:

<?php
function getBetalingskenmerk($getal)
{
    return $betalingskenmerk;
}

In PHP plaats je de argumenten of parameters die aan een functie worden doorgegeven tussen ronde haakjes in de functiedeclaratie. In ons voorbeeld is dat $getal, een variabele waarvoor de functie getBetalingskenmerk() het bijbehorende betalingskenmerk moet retourneren. Vaak is dit getal een factuurnummer. Je kunt een betalingskenmerk ook maken met een klantnummer, abonneenummer of lidmaatschapsnummer, zolang het maar een getal is.

Voor het retourneren of ‘teruggeven’ van een resultaat gebruik je return. In onze voorbeeldfunctie is dat return $betalingskenmerk omdat de functie een betalingskenmerk moet opstellen.

Commentaar in PHPDoc

Webdevelopers bewaren het toevoegen van commentaar vaak voor het laatst. Als ze er later nog aan toekomen… Dat leidt te vaak tot slecht gedocumenteerde webapplicaties. Aangezien we met ons eerste prototype al hebben vastgelegd wat we gaan doen, kunnen we meteen even documenteren wat we in de functie stoppen en wat er daarna uitkomt:

<?php
/**
 * @param mixed $getal Geheel getal of numerieke string.
 * @return string Betalingskenmerk.
 */
function getBetalingskenmerk($getal)
{
    return $betalingskenmerk;
}

Commentaar in PHP wordt door de PHP-parser genegeerd. Het is er alleen voor mensen. Voor de leesbaarheid kan het hier geen kwaad wat extra spaties toe te voegen:

<?php
/**
 * @param  mixed $getal Geheel getal of numerieke string.
 * @return string       Betalingskenmerk.
 */
function getBetalingskenmerk($getal)
{
    return $betalingskenmerk;
}

Met dit prototype hebben we de interface van onze PHP-functie gedocumenteerd. Dat is een belangrijke stap voor het ontwikkelen van een application programming interface (API). Elke PHP-ontwikkelaar kan nu zien welke input de functie verwacht en welke output de functie daarna oplevert.

Vijftien cijfers en een controlecijfer

Een betalingskenmerk bestaat uit vijftien cijfers plus één controlecijfer. Het controlecijfer staat vooraan. Voor de leesbaarheid en om invoerfouten te voorkomen, wordt het betalingskenmerk verdeeld in vier groepen van elk vier cijfers.

Als c het controlecijfer is en hekjes de overige cijfers, is het volledige betalingskenmerk:

c### #### #### ####

Hiermee hebben we een vormvereiste voor het getal dat in de functie wordt gebruikt: het getal moet uiteindelijk uit vijftien cijfers bestaan. Hiervoor kunnen we getallen met minder dan vijftien cijfers aanvullen met voorloopnullen:

function getBetalingskenmerk($getal)
{
    /**
     * Getallen met minder dan 15 cijfers opvullen met voorloopnullen.
     * Bijvoorbeeld 1234567 wordt 000000001234567.
     *
     * @link http://php.net/strlen
     * @link http://php.net/str_pad
     */
    if (strlen($getal) < 15) {
        $getal str_pad($getal15'0'STR_PAD_LEFT);
    }

    return $betalingskenmerk;
}

Gewogen modulus 11

Het controlecijfer in een betalingskenmerk wordt berekend met een methode die de gewogen modulus 11 wordt genoemd. In programmeertermen kunnen we dit een algoritme noemen. De volgende listing toont de drie stappen waarmee de gewogen modulus 11 wordt berekend:

  1. Er wordt een som berekend door de afzonderlijke cijfers van rechts naar links vermenigvuldigen met de reeks 2, 4, 8, 5, 10, 9, 7, 3, 6, 1. Dezelfde reeks wordt hergebruikt voor getallen van meer dan tien cijfers. Deze vermenigvuldiging is de weging in de gewogen modulus 11.
  2. Er wordt een controlegetal berekend als 11 minus de rest van de som gedeeld door 11. In onze PHP-formule is dit de vergelijking $controlegetal = 11 - ($som % 11). De modulus of modulo is het restgetal dat je overhoudt na delen. In PHP kun je voor het berekenen van de modulus de wiskundige operator % gebruiken. Met % 11 krijgen we dus de modulus 11.
  3. Het controlegetal wordt tot slot omgezet in één controlecijfer. Het getal 10 wordt daarbij het cijfer 1 en het getal 11 wordt het cijfer 0 (een nul).

PHP-listing versie 1

<?php
/**
 * @author  Ward van der Put <Ward.van.der.Put@gmail.com>
 * @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
 *
 * @version 1
 *
 * @param  mixed $getal Geheel getal of numerieke string.
 * @return string       Betalingskenmerk.
 */
function getBetalingskenmerk($getal)
{
    /**
     * Getallen met minder dan 15 cijfers opvullen met voorloopnullen.
     *
     * @link http://php.net/strlen
     * @link http://php.net/str_pad
     */
    if (strlen($getal) < 15) {
        $getal str_pad($getal15'0'STR_PAD_LEFT);
    }

    /**
     * Cijfers van rechts naar links vermenigvuldigen met de
     * herhaalde reeks 2, 4, 8, 5, 10, 9, 7, 3, 6, 1.
     */
    $som = (int) 0;
    $som $som + (substr($getal,  -11) *  2);
    $som $som + (substr($getal,  -21) *  4);
    $som $som + (substr($getal,  -31) *  8);
    $som $som + (substr($getal,  -41) *  5);
    $som $som + (substr($getal,  -51) * 10);
    $som $som + (substr($getal,  -61) *  9);
    $som $som + (substr($getal,  -71) *  7);
    $som $som + (substr($getal,  -81) *  3);
    $som $som + (substr($getal,  -91) *  6);
    $som $som + (substr($getal, -101) *  1);
    $som $som + (substr($getal, -111) *  2);
    $som $som + (substr($getal, -121) *  4);
    $som $som + (substr($getal, -131) *  8);
    $som $som + (substr($getal, -141) *  5);
    $som $som + (substr($getal, -151) * 10);
    // Controlegetal berekenen
    $controlegetal 11 - ($som 11);
    // Controlegetal 10 wordt 1
    if ($controlegetal == 10) {
        $controlegetal 1;
    }
    // Controlegetal 11 wordt 0
    if ($controlegetal == 11) {
        $controlegetal 0;
    }

    // Betalingskenmerk samenstellen en retourneren
    $betalingskenmerk $controlegetal $getal;
    $betalingskenmerk chunk_split($betalingskenmerk4' ');
    return $betalingskenmerk;
}

Kleine verbeteringen

De eerste versie van onze PHP-functie voor betalingskenmerken kan op enkele punten worden verbeterd. Niet voor wat betreft de prestaties; daarin is slechts een snelheidswinst te behalen van een paar procent. Wel zijn er wat onderdelen die we netter kunnen programmeren. Hierna vind je drie verbeteringen.

Gecombineerde operatoren

Met een operator duidt je aan welke operatie PHP moet uitvoeren met variabelen en waarden. In de voorbeeldfunctie zijn dat bijvoorbeeld de operator = voor een toewijzing en de operator + voor optellen:

$som = (int) 0;
$som $som + (substr($getal, -11) * 2);
$som $som + (substr($getal, -21) * 4);
$som $som + (substr($getal, -31) * 8);
<...>

In PHP kun je ook gecombineerde operatoren gebruiken. Een voorbeeld is de gecombineerde operator += voor optellen (+) en toewijzen (=). Het $som = $som + … mag je tot $som += … inkorten:

$som = (int) 0;
$som += (substr($getal, -11) * 2);
$som += (substr($getal, -21) * 4);
$som += (substr($getal, -31) * 8);
<...>

De gehele expressie na de gecombineerde operator += staat tussen ronde haakjes, maar die zijn overbodig geworden. Voor de leesbaarheid en de prestaties kunnen we de ronde haakjes beter weglaten:

$som = (int) 0;
$som += substr($getal, -11) * 2;
$som += substr($getal, -21) * 4;
$som += substr($getal, -31) * 8;
<...>

Logische samenhang tonen

Aangezien de berekening 11 - ($som % 11) de getallen 10 en 11 kan opleveren en dat geen losse cijfers zijn, gebruikt de functie de variabele $controlegetal in plaats van $controlecijfer. Het controlegetal wordt pas later een cijfer, want 10 wordt 1 en 11 wordt 0:

// Controlegetal berekenen
$controlegetal 11 -($som 11);
// Controlegetal 10 wordt 1
if ($controlegetal == 10) {
    $controlegetal 1;
}
// Controlegetal 11 wordt 0 (nul)
if ($controlegetal == 11) {
    $controlegetal 0;
}

Aangezien 10 nooit gelijk aan 11 is, hebben we hier twee condities die elkaar altijd uitsluiten. Het omzetten van de controlegetallen 10 en 11 in de controlecijfers 1 en 0 zijn bovendien twee vergelijkbare operaties die altijd moeten worden uitgevoerd. Dat is netter te formaliseren door niet twee keer een if te gebruiken die op zich staat, maar een if met een bijbehorende elseif.

/**
 * Controlegetal berekenen
 *
 * Controlegetal 10 wordt controlecijfer 1 en
 * controlegetal 11 wordt controlecijfer 0 (nul).
 */
$controlegetal 11 - ($som 11);
if ($controlegetal == 10) {
    $controlegetal 1;
} elseif ($controlegetal == 11) {
    $controlegetal 0;
}

Sneller en efficiënter resultaat

Met het controlegetal (dat inmiddels een controlecijfer is) plus het getal van vijftien cijfers kunnen we tot slot het betalingskenmerk samenstellen en retourneren. De PHP-functie chunk_split() splitst hiertoe de string $betalingskenmerk op in vier groepen van vier cijfers, gescheiden door een spatie:

// Betalingskenmerk samenstellen en retourneren
$betalingskenmerk $controlegetal $getal;
$betalingskenmerk chunk_split($betalingskenmerk4' ');
return $betalingskenmerk;

Deze programmeerstijl is duidelijk maar niet efficiënt. We introduceren hier namelijk de nieuwe stringvariabele $betalingskenmerk die slechts bestaat uit twee andere, bestaande variabelen. Hier kunnen we één hulpvariabele plus twee regels PHP-script besparen door onmiddellijk het resultaat te retourneren:

// Betalingskenmerk samenstellen en retourneren
return chunk_split($controlegetal $getal4' ');

PHP-listing versie 2

In de volgende versie van de PHP-functie zijn de eerder genoemde verbeteringen doorgevoerd.

<?php
/**
 * @author  Ward van der Put <Ward.van.der.Put@gmail.com>
 * @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
 *
 * @version 2
 *
 * @param  mixed $getal Geheel getal of numerieke string.
 * @return string       Betalingskenmerk.
 */
function getBetalingskenmerk($getal)
{
    /**
     * Getallen met minder dan 15 cijfers opvullen met voorloopnullen.
     * Bijvoorbeeld 1234567 wordt 000000001234567.
     */
    if (strlen($getal) < 15) {
        $getal str_pad($getal15'0'STR_PAD_LEFT);
    }

    /**
     * Cijfers van rechts naar links vermenigvuldigen met de
     * herhaalde reeks 2, 4, 8, 5, 10, 9, 7, 3, 6, 1.
     */
    $som = (int) 0;
    $som += substr($getal,  -11) *  2;
    $som += substr($getal,  -21) *  4;
    $som += substr($getal,  -31) *  8;
    $som += substr($getal,  -41) *  5;
    $som += substr($getal,  -51) * 10;
    $som += substr($getal,  -61) *  9;
    $som += substr($getal,  -71) *  7;
    $som += substr($getal,  -81) *  3;
    $som += substr($getal,  -91) *  6;
    $som += substr($getal, -101) *  1;
    $som += substr($getal, -111) *  2;
    $som += substr($getal, -121) *  4;
    $som += substr($getal, -131) *  8;
    $som += substr($getal, -141) *  5;
    $som += substr($getal, -151) * 10;

    /**
     * Controlegetal berekenen
     *
     * Controlegetal 10 wordt controlecijfer 1 en
     * controlegetal 11 wordt controlecijfer 0 (nul).
     */
    $controlegetal 11 - ($som 11);
    if ($controlegetal == 10) {
        $controlegetal 1;
    } elseif ($controlegetal == 11) {
        $controlegetal 0;
    }

    // Betalingskenmerk samenstellen en retourneren
    return chunk_split($controlegetal $getal4' ');
}

Functie testen

Met het volgende miniscript kun je de PHP-functie testen:

for ($i 1$i <= 100$i++) {
    $num  mt_rand();
    $kenmerk getBetalingskenmerk($num);
    echo '<pre>Nummer '$num' met kenmerk:<br>'$kenmerk'</pre>';
}

Dit script genereert aselecte (willekeurige) gehele getallen met de PHP-functie mt_rand() en toont het bijbehorende betalingskenmerk, bijvoorbeeld:

Nummer 1070000714 met kenmerk:
9000 0010 7000 0714

Nummer 1786079800 met kenmerk:
9000 0017 8607 9800

Nummer 249635172 met kenmerk:
6000 0002 4963 5172

Ontbreekt er nu nog iets? Ja, we zouden nog wat aan foutafhandeling kunnen doen. Dat bewaren we voor een volgende blogpost over PHP, waarin we een objectgeoriënteerde versie gaan bouwen…

Geen opmerkingen:

Een reactie posten