feat(billing): renewals, coupons, VAT numbering, billing cycles and invoice payment tracking

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
baodev0
2026-10-19 16:37:56 +07:00
co-authored by Claude Sonnet 5.5
parent ea9a53f742
commit 25f23d541e
134 changed files with 4926 additions and 185 deletions
+27
View File
@@ -135,4 +135,31 @@ class EmailTemplateRendererTest extends TestCase
$this->assertSame('Ưu đãi TGS10', $result['subject']);
$this->assertSame('<p>TGS10 cho An</p>{{$content}}', $result['html']);
}
public function test_renewal_variables_are_escaped_exactly_once(): void
{
$template = $this->makeTemplate(
'Gia hạn {{product_name}}',
'<p>{{client_name}} - {{admin_name}}{{server_name_suffix}}</p>',
[
['key' => 'product_name', 'label' => 'Dịch vụ', 'type' => 'string'],
['key' => 'client_name', 'label' => 'Khách', 'type' => 'string'],
['key' => 'admin_name', 'label' => 'Nhân viên', 'type' => 'string'],
['key' => 'server_name_suffix', 'label' => 'Máy chủ', 'type' => 'html'],
],
);
$result = (new EmailTemplateRenderer())->render($template, [
'product_name' => '<script>x</script>',
'client_name' => "A & B's <b>",
'admin_name' => '<img src=x onerror=1>',
'server_name_suffix' => ' (' . e('<i>vps</i>') . ')',
]);
$this->assertSame('Gia hạn &lt;script&gt;x&lt;/script&gt;', $result['subject']);
$this->assertSame(
'<p>A &amp; B&#039;s &lt;b&gt; - &lt;img src=x onerror=1&gt; (&lt;i&gt;vps&lt;/i&gt;)</p>',
$result['html'],
);
}
}