Passed
Push — master ( bbc627...1b6929 )
by Stefan
02:35
created

SepaPmtInf::fromArray()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 13
rs 9.9666
c 0
b 0
f 0
cc 3
nc 3
nop 1
1
<?php
2
namespace SKien\Sepa;
3
4
/**
5
 * Class representing Payment Instruction Info (PII).
6
 *
7
 * #### Usage:
8
 * 1. Create a instance of this class and set all properties using the `SepaPmtInf::setXXX()` methods
9
 *    or `SepaPmtInf::fromArray()`.
10
 * 2. Attach the instance to the `SepaDoc` using the `SepaDoc::addPaymentInstructionInfo()` method.
11
 * 3. Generate the desired transactions (`new SepaTxInf()`) and assign them to this payment info instance.
12
 *
13
 * @package Sepa
14
 * @author Stefanius <[email protected]>
15
 * @copyright MIT License - see the LICENSE file for details
16
 */
17
class SepaPmtInf extends \DOMElement
18
{
19
    use SepaHelper;
20
21
    /** @var string  unique id*/
22
    protected string $id = '';
23
    /** @var string  full name of applicant*/
24
    protected string $strName = '';
25
    /** @var string  IBAN*/
26
    protected string $strIBAN = '';
27
    /** @var string  BIC*/
28
    protected string $strBIC = '';
29
    /** @var string  CI (Creditor Scheme Identification)*/
30
    protected string $strCI = '';
31
    /** @var string  sequence type (Sepa::FRST, Sepa::SEQ_RECURRENT, Sepa::SEQ_ONE_OFF, Sepa::SEQ_FINAL)*/
32
    protected string $strSeqType = '';
33
    /** @var SepaDoc parent document */
34
    private SepaDoc $sepaDoc;
35
    /** @var int count of transactions contained in PII */
36
    private int $iTxCount = 0;
37
    /** @var \DOMElement DOM element containing count of transactions */
38
    private ?\DOMElement $xmlTxCount = null;
39
    /** @var float controlsum */
40
    private float $dblCtrlSum = 0.0;
41
    /** @var \DOMElement DOM element containing controlsum */
42
    private ?\DOMElement $xmlCtrlSum = null;
43
44
    /**
45
     * Creating SEPA Payment Instruction Info (PII).
46
     * @param SepaDoc $sepaDoc
47
     */
48
    function __construct(SepaDoc $sepaDoc)
49
    {
50
        // store the parent doc and generate an unique id
51
        parent::__construct("PmtInf");
52
53
        // don't append any child at this point - created element have to be associated with a document after creation
54
        $this->sepaDoc = $sepaDoc;
55
        $this->id = self::createUID();
56
    }
57
58
    /**
59
     * Validate the object.
60
     * > This method usually dont have to be called from outside. It is
61
     * called, when an instance is attached to a SepaDoc!
62
     * @return int Sepa::OK or error code
63
     * @internal
64
     */
65
    public function validate() : int
66
    {
67
        $iErr = $this->validateIBAN();
68
        $iErr |= $this->validateBIC();
69
        $iErr |= $this->validateCI();
70
        $iErr |= $this->validateMandatory();
71
        return $iErr;
72
    }
73
74
    /**
75
     * Get the error message for given error code.
76
     * Since a payment info can contain multiple errors, the result may contain more than
77
     * one message separated by a separator. <br/>
78
     * The separator can be specified to meet the needs of different output destinations.
79
     * Default value is a linefeed.
80
     * @param int $iError   the errorcode
81
     * @param string $strLF     Separator for multiple errors (default: PHP_EOL; posible values: '&lt;br/&gt;', ';', ...)
82
     * @return string
83
     */
84
    public function errorMsg(int $iError, string $strLF = PHP_EOL) : string
85
    {
86
        // route to the Sepa class to get localized message
87
        return Sepa::errorMsgPmtInf($iError, $strLF);
88
    }
89
90
    /**
91
     * Add a transaction.
92
     * <b>Important:</b><br/>
93
     * The PPI <b>must</b> be attached to the SepaDoc <b>before</b> the first transaktion is added!
94
     * @param SepaTxInf $oTxInf
95
     * @return int Sepa::OK or error code from SepaTxInf::validate()
96
     */
97
    public function addTransaction(SepaTxInf $oTxInf) : int
98
    {
99
        // element must been added as child to valid parent
100
        if ($this->xmlCtrlSum === null || $this->xmlTxCount === null) {
101
            trigger_error('element not added to parent (you must call SepaDoc::addPaymentInstructionInfo() before)!', E_USER_ERROR);
102
        }
103
        // transaction method have to fit to parent doc
104
        if ($this->sepaDoc->getType() != $oTxInf->getType()) {
105
            return Sepa::ERR_TX_INVALID_TYPE;
106
        }
107
108
        $iErr = $oTxInf->validate();
109
        if ($iErr == Sepa::OK) {
110
            if ($oTxInf->getType() == Sepa::CDD) {
111
                $xmlTx = $this->addChild(null, 'DrctDbtTxInf');
112
113
                $xmlNode = $this->addChild($xmlTx, 'PmtId');
114
                $this->addChild($xmlNode, 'EndToEndId', $oTxInf->getPaymentId());
115
116
                // Instructed Amount
117
                $xmlNode = $this->addChild($xmlTx, 'InstdAmt', sprintf("%01.2f", $oTxInf->getValue()));
118
                $xmlNode->SetAttribute('Ccy', 'EUR');
119
120
                // Mandate Related Information
121
                $xmlNode = $this->addChild($xmlTx, 'DrctDbtTx');
122
                $xmlNode = $this->addChild($xmlNode, 'MndtRltdInf');
123
                $this->addChild($xmlNode, 'MndtId', $oTxInf->getMandateId());
124
                $this->addChild($xmlNode, 'DtOfSgntr', $oTxInf->getDateOfSignature());
125
                $this->addChild($xmlNode, 'AmdmntInd', 'false');
126
127
                // Debitor Information  Name, IBAN, BIC
128
                $xmlNode = $this->addChild($xmlTx, 'DbtrAgt');
129
                $xmlNode = $this->addChild($xmlNode, 'FinInstnId');
130
                $this->addChild($xmlNode, 'BIC', $oTxInf->getBIC());
131
132
                $xmlNode = $this->addChild($xmlTx, 'Dbtr');
133
                $this->addChild($xmlNode, 'Nm', $oTxInf->getName());
134
135
                $xmlNode = $this->addChild($xmlTx, 'DbtrAcct');
136
                $xmlNode = $this->addChild($xmlNode, 'Id');
137
                $this->addChild($xmlNode, 'IBAN', $oTxInf->getIBAN());
138
139
                // Ultimate Debitor if requested
140
                $strUltmtDbtr = $oTxInf->getUltimateName();
141
                if (strlen($strUltmtDbtr) > 0) {
142
                    $xmlNode = $this->addChild($xmlTx, 'UltmtDbtr');
143
                    $this->addChild($xmlNode, 'Nm', $strUltmtDbtr);
144
                }
145
            } else {
146
                $xmlTx = $this->addChild(null, 'CdtTrfTxInf');
147
148
                $xmlNode = $this->addChild($xmlTx, 'PmtId');
149
                $this->addChild($xmlNode, 'EndToEndId', $oTxInf->getPaymentId());
150
151
                // Amount
152
                $xmlNode = $this->addChild($xmlTx, 'Amt');
153
                $xmlNode = $this->addChild($xmlNode, 'InstdAmt', sprintf("%01.2f", $oTxInf->getValue()));
154
                $xmlNode->SetAttribute('Ccy', 'EUR');
155
156
                // Creditor Information  Name, IBAN, BIC
157
                $xmlNode = $this->addChild($xmlTx, 'CdtrAgt');
158
                $xmlNode = $this->addChild($xmlNode, 'FinInstnId');
159
                $this->addChild($xmlNode, 'BIC', $oTxInf->getBIC());
160
161
                $xmlNode = $this->addChild($xmlTx, 'Cdtr');
162
                $this->addChild($xmlNode, 'Nm', $oTxInf->getName());
163
164
                $xmlNode = $this->addChild($xmlTx, 'CdtrAcct');
165
                $xmlNode = $this->addChild($xmlNode, 'Id');
166
                $this->addChild($xmlNode, 'IBAN', $oTxInf->getIBAN());
167
168
                // Ultimate Creditor if requested
169
                $strUltmtCbtr = $oTxInf->getUltimateName();
170
                if (strlen($strUltmtCbtr) > 0) {
171
                    $xmlNode = $this->addChild($xmlTx, 'UltmtCbtr');
172
                    $this->addChild($xmlNode, 'Nm', $strUltmtCbtr);
173
                }
174
            }
175
176
            // Remittance Information
177
            $xmlNode = $this->addChild($xmlTx, 'RmtInf');
178
            $this->addChild($xmlNode, 'Ustrd', $oTxInf->getDescription());
179
180
            // calculate count and controlsum of transactions
181
            $this->calc($oTxInf->getValue());
182
        } else {
183
            $this->sepaDoc->incInvalidCount();
184
        }
185
        return $iErr;
186
    }
187
188
    /**
189
     * Create a element and set it as child for given parent.
190
     * @param \DOMElement   $xmlParent  parent for the node. If null, child of current instance is created
191
     * @param string        $strNode    nodename
192
     * @param mixed         $value      nodevalue. If empty, no value will be assigned (to create node only containing child elements)
193
     * @return \DOMElement
194
     */
195
    protected function addChild(?\DOMElement $xmlParent, string $strNode, $value = '') : \DOMElement
196
    {
197
        if ($xmlParent == null) {
198
            $xmlParent = $this;
199
        }
200
        $xmlNode = $this->sepaDoc->createElement($strNode);
201
        if (!empty($value)) {
202
            $xmlNode->nodeValue = $value;
203
        }
204
        $xmlParent->appendChild($xmlNode);
205
206
        return $xmlNode;
207
    }
208
209
    /**
210
     * Calculate transaction count and controlsum for PII and update overall in parent doc.
211
     * @param float $dblValue
212
     */
213
    protected function calc(float $dblValue) : void
214
    {
215
        if ($this->xmlTxCount !== null && $this->xmlCtrlSum !== null) {
216
            $this->iTxCount++;
217
            $this->xmlTxCount->nodeValue = (string)$this->iTxCount;
218
            $this->dblCtrlSum += $dblValue;
219
            $this->xmlCtrlSum->nodeValue = sprintf("%01.2f", $this->dblCtrlSum);
220
221
            $this->sepaDoc->calc($dblValue);
222
        }
223
    }
224
225
    /**
226
     * Return the internal unique ID.
227
     * @return string
228
     */
229
    public function getId() : string
230
    {
231
        return $this->id;
232
    }
233
234
    /**
235
     * Get the collection date.
236
     * @return string
237
     */
238
    public function getCollectionDate() : string
239
    {
240
        // Requested Collection Date depends on sequence type
241
        $this->strSeqType == Sepa::SEQ_RECURRENT ? $iDays = 3 : $iDays = 6;
242
        $dtCollect = self::calcCollectionDate($iDays);
243
        return date('Y-m-d', $dtCollect);
244
    }
245
246
    /**
247
     * Set the xml node containing transactions count.
248
     * @param \DOMElement $xmlNode
249
     */
250
    public function setTxCountNode(\DOMElement $xmlNode) : void
251
    {
252
        $this->xmlTxCount = $xmlNode;
253
    }
254
255
    /**
256
     * Set the xml node containing control sum.
257
     * @param \DOMElement $xmlNode
258
     */
259
    public function setCtrlSumNode(\DOMElement $xmlNode) : void
260
    {
261
        $this->xmlCtrlSum = $xmlNode;
262
    }
263
264
    /**
265
     * Set properties through associative array.
266
     * Example array:
267
     * ```php
268
     *   $aPPI = [
269
     *       'strName' => '<name>',
270
     *       'strCI' => '<CI>',
271
     *       'strIBAN' => '<IBAN>',
272
     *       'strBIC' => '<BIC>',
273
     *       'strSeqType' => Sepa::SEQ_xxx,
274
     *   ];
275
     * ```
276
     * The array does not have to contain all of the properties. The missing properties
277
     * can be set later using the respective `setXXX()` method.
278
     *
279
     * @param array<string> $aProperties    see description
280
     */
281
    public function fromArray(array $aProperties) : void
282
    {
283
        // use the setter methods to ensure that all validations are made!
284
        $aPropertyMap = [
285
            'strName' => 'setName',
286
            'strIBAN' => 'setIBAN',
287
            'strBIC' => 'setBIC',
288
            'strCI' => 'setCI',
289
            'strSeqType' => 'setSeqType',
290
        ];
291
        foreach ($aPropertyMap as $strKey => $strFunc) {
292
            if (isset($aProperties[$strKey])) {
293
                $this->$strFunc($aProperties[$strKey]);
294
            }
295
        }
296
    }
297
298
    /**
299
     * Set full name (lastname, firstname; company name; ...).
300
     * @param string $strName
301
     */
302
    public function setName(string $strName) : void
303
    {
304
        $this->strName = self::validString($strName, Sepa::MAX70);
305
    }
306
307
    /**
308
     * Set IBAN.
309
     * @param string $strIBAN
310
     */
311
    public function setIBAN(string $strIBAN) : void
312
    {
313
        $this->strIBAN = $strIBAN;
314
    }
315
316
    /**
317
     * Set BIC.
318
     * @param string $strBIC
319
     */
320
    public function setBIC(string $strBIC) : void
321
    {
322
        $this->strBIC = $strBIC;
323
    }
324
325
    /**
326
     * Sset CI (Creditor Scheme Identification).
327
     * @param string $strCI
328
     */
329
    public function setCI(string $strCI) : void
330
    {
331
        $this->strCI = $strCI;
332
    }
333
334
    /**
335
     * Set sequence type (Sepa::FRST, Sepa::SEQ_RECURRENT, Sepa::SEQ_ONE_OFF, Sepa::SEQ_FINAL).
336
     * @param string $strSeqType
337
     */
338
    public function setSeqType(string $strSeqType) : void
339
    {
340
        $this->strSeqType = $strSeqType;
341
    }
342
343
    /**
344
     * Get full name (lastname, firstname; company name; ...).
345
     * @return string
346
     */
347
    public function getName() : string
348
    {
349
        return $this->strName;
350
    }
351
352
    /**
353
     * Get IBAN.
354
     * @return string
355
     */
356
    public function getIBAN() : string
357
    {
358
        return $this->strIBAN;
359
    }
360
361
    /**
362
     * Get BIC.
363
     * @return string
364
     */
365
    public function getBIC() : string
366
    {
367
        return $this->strBIC;
368
    }
369
370
    /**
371
     * Get CI (Creditor Scheme Identification).
372
     * @return string
373
     */
374
    public function getCI() : string
375
    {
376
        return $this->strCI;
377
    }
378
379
    /**
380
     * Get sequence type (Sepa::FRST, Sepa::SEQ_RECURRENT, Sepa::SEQ_ONE_OFF, Sepa::SEQ_FINAL).
381
     * @return string
382
     */
383
    public function getSeqType() : string
384
    {
385
        return $this->strSeqType;
386
    }
387
388
    /**
389
     * Validate the IBAN.
390
     * @return int Sepa::OK or errorcode
391
     */
392
    private function validateIBAN() : int
393
    {
394
        $iErr = Sepa::OK;
395
        if (!Sepa::checkValidation(Sepa::V_NO_IBAN_VALIDATION)) {
396
            if (strlen($this->strIBAN) == 0) {
397
                $iErr = Sepa::ERR_PMT_IBAN_MISSING;
398
            } else if (Sepa::validateIBAN($this->strIBAN) != Sepa::OK) {
399
                $iErr = Sepa::ERR_PMT_INVALID_IBAN;
400
            }
401
        }
402
        return $iErr;
403
    }
404
405
    /**
406
     * Validate the BIC.
407
     * @return int Sepa::OK or errorcode
408
     */
409
    private function validateBIC() : int
410
    {
411
        $iErr = Sepa::OK;
412
        if (!Sepa::checkValidation(Sepa::V_NO_BIC_VALIDATION)) {
413
            if (strlen($this->strBIC) == 0) {
414
                $iErr = Sepa::ERR_PMT_BIC_MISSING;
415
            } else if (Sepa::validateBIC($this->strBIC) != Sepa::OK) {
416
                $iErr = Sepa::ERR_PMT_INVALID_BIC;
417
            }
418
        }
419
        return $iErr;
420
    }
421
422
    /**
423
     * Validate the CI.
424
     * @return int Sepa::OK or errorcode
425
     */
426
    private function validateCI() : int
427
    {
428
        $iErr = Sepa::OK;
429
        if (!Sepa::checkValidation(Sepa::V_NO_CI_VALIDATION)) {
430
            if (strlen($this->strCI) == 0) {
431
                $iErr = Sepa::ERR_PMT_CI_MISSING;
432
            } else if (Sepa::validateCI($this->strCI) != Sepa::OK) {
433
                $iErr = Sepa::ERR_PMT_INVALID_CI;
434
            }
435
        }
436
        return $iErr;
437
    }
438
439
    /**
440
     * Validate mandatory fields.
441
     * @return int Sepa::OK or errorcode
442
     */
443
    private function validateMandatory() : int
444
    {
445
        $iErr = Sepa::OK;
446
        if (!Sepa::checkValidation(Sepa::V_IGNORE_MISSING_VALUE)) {
447
            if (strlen($this->strName) == 0) {
448
                $iErr |= Sepa::ERR_PMT_NAME_MISSING;
449
            }
450
            if (strlen($this->strSeqType) == 0) {
451
                $iErr |= Sepa::ERR_PMT_SEQ_TYPE_MISSING;
452
            } else if (!$this->isValidateSeqType($this->strSeqType)) {
453
                $iErr |= Sepa::ERR_PMT_INVALID_SEQ_TYPE;
454
            }
455
        }
456
        return $iErr;
457
    }
458
459
    /**
460
     * Check for valid sequence type.
461
     * @param string $strSeqType type to check
462
     * @return bool
463
     */
464
    private function isValidateSeqType(string $strSeqType) : bool
465
    {
466
        $aValid = [
467
            Sepa::SEQ_FIRST,
468
            Sepa::SEQ_RECURRENT,
469
            Sepa::SEQ_ONE_OFF,
470
            Sepa::SEQ_FINAL
471
        ];
472
        return in_array($strSeqType, $aValid);
473
    }
474
}
475