• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
Build has been canceled!

AJenbo / agcms / 20972217862

13 Jan 2026 08:53PM UTC coverage: 53.678% (+0.1%) from 53.541%
20972217862

Pull #74

github

web-flow
Merge 4fdfac7ee into 498ff829e
Pull Request #74: Add PHP versions 8.4 to 8.5 to CI matrix

248 of 345 new or added lines in 40 files covered. (71.88%)

6 existing lines in 5 files now uncovered.

2780 of 5179 relevant lines covered (53.68%)

13.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

52.36
/application/inc/Services/InvoiceService.php
1
<?php
2

3
namespace App\Services;
4

5
use App\Countries;
6
use App\Enums\ColumnType;
7
use App\Enums\InvoiceAction;
8
use App\Enums\InvoiceStatus;
9
use App\Exceptions\Exception;
10
use App\Exceptions\InvalidInput;
11
use App\Models\Contact;
12
use App\Models\Email;
13
use App\Models\Epayment;
14
use App\Models\Invoice;
15
use App\Models\Page;
16
use App\Models\Table;
17
use App\Models\User;
18

19
class InvoiceService
20
{
21
    /**
22
     * Create an invoice from the client cart array.
23
     *
24
     * @param array<mixed> $cart
25
     */
26
    public function createFromCart(array $cart): Invoice
27
    {
28
        $db = app(DbService::class);
13✔
29
        $orm = app(OrmService::class);
13✔
30
        $amount = 0;
13✔
31
        $items = [];
13✔
32
        if (isset($cart['items']) && is_array($cart['items'])) {
13✔
33
            foreach ($cart['items'] as $item) {
13✔
34
                if (!is_array($item)) {
10✔
35
                    continue;
×
36
                }
37
                $title = '';
10✔
38
                $quantity = $item['quantity'] ?? null;
10✔
39
                if (!is_int($quantity) && (!is_string($quantity) || !ctype_digit($quantity))) {
10✔
40
                    $quantity = 0;
×
41
                }
42
                $quantity = (int)$quantity;
10✔
43
                $value = null;
10✔
44
                $pageId = null;
10✔
45
                if ('line' === $item['type']) { // Find item based on price table row
10✔
46
                    $db->addLoadedTable('list_rows');
2✔
47
                    $listRow = $db->fetchOne('SELECT * FROM `list_rows` WHERE id = ' . valint($item['id']));
2✔
48
                    $table = $listRow ? $orm->getOne(Table::class, (int)$listRow['list_id']) : null;
2✔
49
                    if ($table) {
2✔
50
                        $pageId = $table->getPage()->getId();
1✔
51
                        if ($table->hasLinks() && $listRow['link']) {
1✔
52
                            $pageId = (int)$listRow['link'];
1✔
53
                        }
54

55
                        $cells = explode('<', $listRow['cells']);
1✔
56
                        $cells = array_map('html_entity_decode', $cells);
1✔
57

58
                        foreach ($table->getColumns() as $i => $column) {
1✔
59
                            if (empty($cells[$i]) || !mb_trim($cells[$i])) {
1✔
60
                                continue;
×
61
                            }
62

63
                            if (in_array($column->type, [ColumnType::String, ColumnType::Int], true)) {
1✔
64
                                $title .= ' ' . mb_trim($cells[$i]);
1✔
65
                            } elseif (in_array(
1✔
66
                                $column->type,
1✔
67
                                [ColumnType::Price, ColumnType::SalesPrice],
68
                                true
69
                            )) {
70
                                $value = (int)$cells[$i];
1✔
71
                            }
72
                        }
73
                        $title = mb_trim($title);
2✔
74
                    }
75
                } elseif ('page' === $item['type']) {
8✔
76
                    $pageId = intOrNull($item['id'] ?? null);
8✔
77
                }
78

79
                $page = $pageId ? $orm->getOne(Page::class, $pageId) : null;
10✔
80
                if (!$page || $page->isInactive()) {
10✔
81
                    $title = _('Expired');
3✔
82
                } else {
83
                    if (!$title) {
7✔
84
                        $title = $page->getTitle();
6✔
85
                        if ($page->getSku()) {
6✔
86
                            if ($title) {
3✔
87
                                $title .= ' - ';
3✔
88
                            }
89

90
                            $title .= $page->getSku();
3✔
91
                        }
92
                    }
93
                    if (!$value || 'page' === $item['type']) {
7✔
94
                        $value = $value ?: $page->getPrice();
6✔
95
                    }
96
                }
97

98
                $items[] = [
10✔
99
                    'title'    => mb_trim($title),
10✔
100
                    'quantity' => $quantity,
101
                    'value'    => $value,
102
                ];
103

104
                $amount += $value * $quantity;
10✔
105
            }
106
        }
107

108
        $items = json_encode($items, JSON_THROW_ON_ERROR);
13✔
109

110
        $addressData = $this->cleanAddressData($cart);
13✔
111

112
        return new Invoice($addressData + [
13✔
113
            'item_data' => $items,
114
            'amount'    => $amount,
115
            'note'      => $cart['note'] ?? '',
13✔
116
        ]);
117
    }
118

119
    /**
120
     * Clean up address data.
121
     *
122
     * @param array<mixed> $data
123
     *
124
     * @return array<string, mixed>
125
     */
126
    public function cleanAddressData(array $data): array
127
    {
128
        $data = [
19✔
129
            'name'                 => $data['name'] ?? '',
19✔
130
            'attn'                 => $data['attn'] ?? '',
19✔
131
            'address'              => $data['address'] ?? '',
19✔
132
            'postbox'              => $data['postbox'] ?? '',
19✔
133
            'postcode'             => $data['postcode'] ?? '',
19✔
134
            'city'                 => $data['city'] ?? '',
19✔
135
            'country'              => $data['country'] ?? 'DK',
19✔
136
            'email'                => $data['email'] ?? '',
19✔
137
            'phone1'               => $data['phone1'] ?? '',
19✔
138
            'phone2'               => $data['phone2'] ?? '',
19✔
139
            'has_shipping_address' => (bool)($data['hasShippingAddress'] ?? false),
19✔
140
            'shipping_phone'       => $data['shippingPhone'] ?? '',
19✔
141
            'shipping_name'        => $data['shippingName'] ?? '',
19✔
142
            'shipping_attn'        => $data['shippingAttn'] ?? '',
19✔
143
            'shipping_address'     => $data['shippingAddress'] ?? '',
19✔
144
            'shipping_address2'    => $data['shippingAddress2'] ?? '',
19✔
145
            'shipping_postbox'     => $data['shippingPostbox'] ?? '',
19✔
146
            'shipping_postcode'    => $data['shippingPostcode'] ?? '',
19✔
147
            'shipping_city'        => $data['shippingCity'] ?? '',
19✔
148
            'shipping_country'     => $data['shippingCountry'] ?? 'DK',
19✔
149
        ];
150
        if ($data['attn'] === $data['name']) {
19✔
151
            $data['attn'] = '';
9✔
152
        }
153
        if ($data['postbox'] === $data['postcode']) {
19✔
154
            $data['postbox'] = '';
8✔
155
        }
156
        if ($data['phone1'] === $data['phone2']) {
19✔
157
            $data['phone1'] = '';
12✔
158
        }
159
        if ($data['shipping_attn'] === $data['shipping_name']) {
19✔
160
            $data['shipping_attn'] = '';
13✔
161
        }
162
        if ($data['shipping_postbox'] === $data['shipping_postcode']) {
19✔
163
            $data['shipping_postbox'] = '';
13✔
164
        }
165
        if (!$data['shipping_address2']
19✔
166
            && ($data['shipping_phone'] === $data['phone1'] || $data['shipping_phone'] === $data['phone2'])
19✔
167
            && $data['shipping_name'] === $data['name']
19✔
168
            && $data['shipping_attn'] === $data['attn']
19✔
169
            && $data['shipping_address'] === $data['address']
19✔
170
            && $data['shipping_postbox'] === $data['postbox']
19✔
171
            && $data['shipping_postcode'] === $data['postcode']
19✔
172
            && $data['shipping_city'] === $data['city']
19✔
173
            && $data['shipping_country'] === $data['country']
19✔
174
        ) {
175
            $data['has_shipping_address'] = false;
8✔
176
        }
177

178
        return $data;
19✔
179
    }
180

181
    /**
182
     * Generate additional order comments based on cart options.
183
     *
184
     * @param array<mixed> $cart
185
     */
186
    public function generateExtraNote(array $cart): string
187
    {
188
        $notes = [];
4✔
189
        switch (valstring($cart['payMethod'] ?? '')) {
4✔
190
            case 'creditcard':
4✔
191
                $notes[] = _('I would like to pay via credit card.');
2✔
192
                break;
2✔
193
            case 'bank':
2✔
194
                $notes[] = _('I would like to pay via bank transaction.');
×
195
                break;
×
196
            case 'cash':
2✔
197
                $notes[] = _('I would like to pay via cash.');
×
198
                break;
×
199
        }
200
        switch (valstring($cart['deleveryMethod'] ?? '')) {
4✔
201
            case 'pickup':
4✔
202
                $notes[] = _('I will pick up the goods in your shop.');
×
203
                break;
×
204
            case 'postal':
4✔
205
                $notes[] = _('Please send the goods by mail.');
2✔
206
                break;
2✔
207
        }
208

209
        return implode("\n", $notes);
4✔
210
    }
211

212
    /**
213
     * Add the customer to the malinglist.
214
     */
215
    public function addToAddressBook(Invoice $invoice, ?string $clientIp): void
216
    {
217
        /** @var string[] */
218
        $countries = Countries::getOrdered();
3✔
219
        $conteact = app(OrmService::class)->getOneByQuery(
3✔
220
            Contact::class,
221
            'SELECT * FROM email WHERE email = ' . app(DbService::class)->quote($invoice->getEmail())
3✔
222
        );
223
        if (!$conteact) {
3✔
224
            $conteact = new Contact([
2✔
225
                'name'       => $invoice->getName(),
2✔
226
                'email'      => $invoice->getEmail(),
2✔
227
                'address'    => $invoice->getAddress(),
2✔
228
                'country'    => $countries[$invoice->getCountry()] ?? '',
2✔
229
                'postcode'   => $invoice->getPostcode(),
2✔
230
                'city'       => $invoice->getCity(),
2✔
231
                'phone1'     => $invoice->getPhone1(),
2✔
232
                'phone2'     => $invoice->getPhone2(),
2✔
233
                'subscribed' => true,
234
                'ip'         => $clientIp ?? '',
235
            ]);
236
            $conteact->save();
2✔
237

238
            return;
2✔
239
        }
240

241
        $conteact->setName($invoice->getName())
1✔
242
            ->setEmail($invoice->getEmail())
1✔
243
            ->setAddress($invoice->getAddress())
1✔
244
            ->setCountry($countries[$invoice->getCountry()] ?? '')
1✔
245
            ->setPostcode($invoice->getPostcode())
1✔
246
            ->setCity($invoice->getCity())
1✔
247
            ->setPhone1($invoice->getPhone1())
1✔
248
            ->setPhone2($invoice->getPhone2())
1✔
249
            ->setPhone2($invoice->getPhone2())
1✔
250
            ->setSubscribed(true)
1✔
251
            ->setIp($clientIp ?? '')
1✔
252
            ->save();
1✔
253
    }
254

255
    /**
256
     * Update invoice and mange it's state.
257
     *
258
     * @param array<mixed> $updates
259
     */
260
    public function invoiceBasicUpdate(Invoice $invoice, User $user, InvoiceAction $action, array $updates): void
261
    {
262
        $status = $invoice->getStatus();
×
263

264
        if (InvoiceStatus::New === $invoice->getStatus()) {
×
265
            if (InvoiceAction::Lock === $action) {
×
266
                $status = InvoiceStatus::Locked;
×
267
            }
NEW
268
            $invoice->setTimeStamp(strtotime(valstring($updates['date'])) ?: time());
×
NEW
269
            $invoice->setShipping(valfloat($updates['shipping']));
×
NEW
270
            $invoice->setAmount(valfloat($updates['amount']));
×
NEW
271
            $invoice->setVat(valfloat($updates['vat']));
×
NEW
272
            $invoice->setPreVat(valbool($updates['preVat']));
×
NEW
273
            $invoice->setIref(valstring($updates['iref']));
×
NEW
274
            $invoice->setEref(valstring($updates['eref']));
×
NEW
275
            $invoice->setName(valstring($updates['name']));
×
NEW
276
            $invoice->setAttn(valstring($updates['attn']));
×
NEW
277
            $invoice->setAddress(valstring($updates['address']));
×
NEW
278
            $invoice->setPostbox(valstring($updates['postbox']));
×
NEW
279
            $invoice->setPostcode(valstring($updates['postcode']));
×
NEW
280
            $invoice->setCity(valstring($updates['city']));
×
NEW
281
            $invoice->setCountry(valstring($updates['country']));
×
NEW
282
            $invoice->setEmail(valstring($updates['email']));
×
NEW
283
            $invoice->setPhone1(valstring($updates['phone1']));
×
NEW
284
            $invoice->setPhone2(valstring($updates['phone2']));
×
NEW
285
            $invoice->setHasShippingAddress(valbool($updates['hasShippingAddress']));
×
286
            if ($updates['hasShippingAddress']) {
×
NEW
287
                $invoice->setShippingPhone(valstring($updates['shippingPhone']));
×
NEW
288
                $invoice->setShippingName(valstring($updates['shippingName']));
×
NEW
289
                $invoice->setShippingAttn(valstring($updates['shippingAttn']));
×
NEW
290
                $invoice->setShippingAddress(valstring($updates['shippingAddress']));
×
NEW
291
                $invoice->setShippingAddress2(valstring($updates['shippingAddress2']));
×
NEW
292
                $invoice->setShippingPostbox(valstring($updates['shippingPostbox']));
×
NEW
293
                $invoice->setShippingPostcode(valstring($updates['shippingPostcode']));
×
NEW
294
                $invoice->setShippingCity(valstring($updates['shippingCity']));
×
NEW
295
                $invoice->setShippingCountry(valstring($updates['shippingCountry']));
×
296
            }
297
            $invoice->setItemData(json_encode($updates['lines'], JSON_THROW_ON_ERROR) ?: '[]');
×
298
        }
299

300
        if (isset($updates['note']) && is_string($updates['note'])) {
×
301
            $note = $updates['note'];
×
302
            if (InvoiceStatus::New !== $invoice->getStatus()) {
×
NEW
303
                $note = mb_trim($invoice->getNote() . "\n" . $note);
×
304
            }
305
            $invoice->setNote($note);
×
306
        }
307

308
        $internalNote = $updates['internalNote'] ?? null;
×
309
        if (!is_string($internalNote)) {
×
310
            $internalNote = '';
×
311
        }
312
        $invoice->setInternalNote($internalNote);
×
313

314
        if (!$invoice->getDepartment() && 1 === count(ConfigService::getEmailConfigs())) {
×
315
            $email = ConfigService::getDefaultEmail();
×
316
            $invoice->setDepartment($email);
×
317
        } elseif (!empty($updates['department']) && is_string($updates['department'])) {
×
318
            $invoice->setDepartment($updates['department']);
×
319
        }
320

321
        if (!$invoice->getClerk()) {
×
322
            $invoice->setClerk($user->getFullName());
×
323
        }
324

325
        if (in_array($invoice->getStatus(), [
×
326
            InvoiceStatus::New,
327
            InvoiceStatus::Locked,
328
            InvoiceStatus::Rejected,
329
        ], true)) {
330
            if (InvoiceAction::Giro === $action) {
×
331
                $status = InvoiceStatus::Giro;
×
332
            }
333
            if (InvoiceAction::Cash === $action) {
×
334
                $status = InvoiceStatus::Cash;
×
335
            }
336
        }
337

338
        if (!$invoice->isHandled()) {
×
339
            if (in_array($action, [InvoiceAction::Cancel, InvoiceAction::Giro, InvoiceAction::Cash], true)
×
340
                || (InvoiceAction::Lock === $action && InvoiceStatus::Locked !== $invoice->getStatus())
×
341
            ) {
342
                $date = $updates['paydate'] ?? null;
×
343
                if (!is_string($date)) {
×
344
                    $date = '';
×
345
                }
346
                $invoice->setTimeStampPay(strtotime($date) ?: time());
×
347
            }
348

349
            if (InvoiceAction::Cancel === $action) {
×
350
                if (InvoiceStatus::PbsOk === $invoice->getStatus()) {
×
351
                    $this->annulPayment($invoice);
×
352
                }
353
                $status = InvoiceStatus::Canceled;
×
354
            }
355

356
            $clerk = $updates['clerk'] ?? null;
×
357
            if (is_string($clerk) && $user->hasAccess(User::ADMINISTRATOR)) {
×
358
                $invoice->setClerk($clerk);
×
359
            }
360
        }
361

362
        $invoice->setStatus($status)->save();
×
363
    }
364

365
    /**
366
     * Accept payment.
367
     *
368
     * @throws Exception
369
     */
370
    public function capturePayment(Invoice $invoice): void
371
    {
372
        $epayment = $this->getPayment($invoice);
×
373
        if (!$epayment->confirm()) {
×
374
            throw new Exception(_('Failed to capture payment.'));
×
375
        }
376

377
        $invoice->setStatus(InvoiceStatus::Accepted)
×
378
            ->setTimeStampPay(time())
×
379
            ->save();
×
380
    }
381

382
    /**
383
     * Cancle payment.
384
     *
385
     * @throws Exception
386
     */
387
    public function annulPayment(Invoice $invoice): void
388
    {
389
        $epayment = $this->getPayment($invoice);
×
390
        if (!$epayment->annul()) {
×
391
            throw new Exception(_('Failed to cancel payment.'));
×
392
        }
393

394
        if (InvoiceStatus::PbsOk === $invoice->getStatus()) {
×
395
            $invoice->setStatus(InvoiceStatus::Rejected)->save();
×
396
        }
397
    }
398

399
    /**
400
     * Get payment.
401
     */
402
    private function getPayment(Invoice $invoice): Epayment
403
    {
404
        $epaymentService = new EpaymentService(ConfigService::getString('pbsid'), ConfigService::getString('pbspwd'));
×
405

406
        return $epaymentService->getPayment(ConfigService::getString('pbsfix') . $invoice->getId());
×
407
    }
408

409
    /**
410
     * Send payment email to client.
411
     *
412
     * @throws InvalidInput
413
     */
414
    public function sendInvoice(Invoice $invoice): void
415
    {
416
        if (!$invoice->hasValidEmail()) {
×
417
            throw new InvalidInput(_('Email is not valid.'));
×
418
        }
419

420
        if (!$invoice->getDepartment() && 1 === count(ConfigService::getEmailConfigs())) {
×
421
            $email = ConfigService::getDefaultEmail();
×
422
            $invoice->setDepartment($email);
×
423
        } elseif (!$invoice->getDepartment()) {
×
424
            throw new InvalidInput(_('You have not selected a sender.'));
×
425
        }
426
        if ($invoice->getAmount() < 0.01) {
×
427
            throw new InvalidInput(_('The invoice must be of at least 1 cent.'));
×
428
        }
429

430
        $subject = _('Online payment for ') . ConfigService::getString('site_name');
×
431
        $emailTemplate = 'email/invoice';
×
432
        if ($invoice->isSent()) {
×
433
            $subject = 'Elektronisk faktura vedr. ordre';
×
434
            $emailTemplate = 'email/invoice-reminder';
×
435
        }
436

437
        $emailBody = app(RenderService::class)->render(
×
438
            $emailTemplate,
439
            [
440
                'invoice'    => $invoice,
441
                'siteName'   => ConfigService::getString('site_name'),
×
442
                'address'    => ConfigService::getString('address'),
×
443
                'postcode'   => ConfigService::getString('postcode'),
×
444
                'city'       => ConfigService::getString('city'),
×
445
                'phone'      => ConfigService::getString('phone'),
×
446
            ]
447
        );
448

449
        $email = new Email([
×
450
            'subject'          => $subject,
451
            'body'             => $emailBody,
452
            'senderName'       => ConfigService::getString('site_name'),
×
453
            'senderAddress'    => $invoice->getDepartment(),
×
454
            'recipientName'    => $invoice->getName(),
×
455
            'recipientAddress' => $invoice->getEmail(),
×
456
        ]);
457

458
        app(EmailService::class)->send($email);
×
459

460
        if (InvoiceStatus::New === $invoice->getStatus()) {
×
461
            $invoice->setStatus(InvoiceStatus::Locked);
×
462
        }
463

464
        $invoice->setSent(true)
×
465
            ->save();
×
466
    }
467
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc