Skip to content

Commit fb44626

Browse files
FaustoJohnmarkharding
authored andcommitted
Feat/gift card purchase minds/engine#2582
1 parent 7146889 commit fb44626

39 files changed

+1042
-111
lines changed

Common/Regex.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ class Regex
1919
// #tags | $tags
2020
const HASH_CASH_TAG = '/([^&]|\b|^)#([\wÀ-ÿ\x0E00\x0E7F\x2460-\x9FBB]+)|([^&]|\b|^)\$([A-Za-z]+)/uim';
2121

22+
const EMAIL = '/[^\\\\|^\\/|^\\?]*@([\\w-]+\\.)+[\\w-]{2,3}/A';
23+
24+
const GUID_OR_EMAIL = '/[0-9]+|[^\\\\|^\\/|^\\?]*@([\\w-]+\\.)+[\\w-]{2,3}/A';
25+
const GUID = "/[0-9]+/A";
26+
2227
/**
2328
* Wrapper around preg_match_all for testing.
2429
*/

Controllers/Cli/GiftCards.php

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,43 @@
11
<?php
2+
declare(strict_types=1);
23

34
namespace Minds\Controllers\Cli;
45

56
use Exception;
7+
use InvalidArgumentException;
68
use Minds\Cli;
79
use Minds\Core\Di\Di;
810
use Minds\Core\EntitiesBuilder;
11+
use Minds\Core\Log\Logger;
12+
use Minds\Core\Payments\GiftCards\Enums\GiftCardPaymentTypeEnum;
913
use Minds\Core\Payments\GiftCards\Enums\GiftCardProductIdEnum;
14+
use Minds\Core\Payments\GiftCards\Exceptions\GiftCardPaymentFailedException;
1015
use Minds\Core\Payments\GiftCards\Manager;
16+
use Minds\Core\Payments\GiftCards\Types\GiftCardTarget;
17+
use Minds\Core\Payments\Stripe\Exceptions\StripeTransferFailedException;
1118
use Minds\Core\Payments\V2\Manager as PaymentsManager;
1219
use Minds\Core\Payments\V2\Models\PaymentDetails;
1320
use Minds\Entities\User;
21+
use Minds\Exceptions\ServerErrorException;
22+
use Minds\Exceptions\UserErrorException;
1423
use Minds\Interfaces;
24+
use Stripe\Exception\ApiErrorException;
1525

1626
class GiftCards extends Cli\Controller implements Interfaces\CliControllerInterface
1727
{
1828
private Manager $giftCardsManager;
1929
private PaymentsManager $paymentsManager;
2030
private EntitiesBuilder $entitiesBuilder;
2131

32+
private readonly Logger $logger;
33+
2234
public function __construct(
2335
) {
2436
$this->giftCardsManager = Di::_()->get(Manager::class);
2537
$this->paymentsManager = Di::_()->get(PaymentsManager::class);
2638
$this->entitiesBuilder = Di::_()->get('EntitiesBuilder');
39+
$this->logger = Di::_()->get('Logger');
40+
2741
Di::_()->get('Config')
2842
->set('min_log_level', 'INFO');
2943
}
@@ -42,7 +56,16 @@ public function exec(): void
4256
ini_set('display_errors', 1);
4357
}
4458

45-
public function createTestCard()
59+
/**
60+
* @return void
61+
* @throws GiftCardPaymentFailedException
62+
* @throws StripeTransferFailedException
63+
* @throws ServerErrorException
64+
* @throws UserErrorException
65+
* @throws ApiErrorException
66+
* @throws Exception
67+
*/
68+
public function createTestCard(): void
4669
{
4770
$userGuid = $this->getOpt('user_guid');
4871

@@ -56,9 +79,22 @@ public function createTestCard()
5679
issuer: $user,
5780
productId: GiftCardProductIdEnum::BOOST,
5881
amount: 10,
82+
stripePaymentMethodId: $this->getOpt('stripe_payment_method_id') ?? "",
83+
giftCardPaymentTypeEnum: GiftCardPaymentTypeEnum::tryFrom((int)$this->getOpt('payment_type')) ?? throw new InvalidArgumentException('Invalid payment type')
5984
);
6085

61-
var_dump($giftCard);
86+
$this->giftCardsManager->sendGiftCardToRecipient(
87+
recipient: new GiftCardTarget(
88+
targetUserGuid: ((int) $this->getOpt('recipient_user_guid')) ?? null,
89+
targetEmail: $this->getOpt('recipient_email'),
90+
),
91+
giftCard: $giftCard,
92+
);
93+
94+
$this->logger->info('Gift card created', [
95+
'gift_card_guid' => $giftCard->guid,
96+
'gift_card_code' => $giftCard->claimCode,
97+
]);
6298
}
6399

64100
public function getGiftCard()

Core/Email/Mailer.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use Minds\Core\Di\Di;
99
use Minds\Core\Log\Logger;
1010
use Minds\Core\Queue\Client as Queue;
11-
use Minds\Entities;
1211
use PHPMailer;
1312

1413
class Mailer
@@ -118,6 +117,11 @@ public function getStats()
118117
return $this->stats;
119118
}
120119

120+
public function getErrors(): string
121+
{
122+
return $this->mailer->ErrorInfo;
123+
}
124+
121125
public function __destruct()
122126
{
123127
if ($this->mailer) {

Core/Email/Module.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class Module implements ModuleInterface
1111
{
1212
public array $submodules = [
1313
Mautic\Module::class,
14+
V2\Module::class,
1415
];
1516

1617
/**

Core/Email/V2/Campaigns/Recurring/BoostV3/BoostEmailer.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@
1010
use Exception;
1111
use Minds\Core\Boost\V3\Enums\BoostPaymentMethod;
1212
use Minds\Core\Boost\V3\Enums\BoostTargetAudiences;
13+
use Minds\Core\Boost\V3\Models\Boost;
14+
use Minds\Core\Boost\V3\Utils\BoostConsoleUrlBuilder;
15+
use Minds\Core\Boost\V3\Utils\BoostReceiptUrlBuilder;
1316
use Minds\Core\Config\Config;
1417
use Minds\Core\Di\Di;
1518
use Minds\Core\Email\Campaigns\EmailCampaign;
1619
use Minds\Core\Email\Mailer;
1720
use Minds\Core\Email\V2\Common\Message;
1821
use Minds\Core\Email\V2\Common\Template;
1922
use Minds\Core\Email\V2\Partials\ActionButtonV2\ActionButtonV2;
20-
use Minds\Core\Boost\V3\Models\Boost;
21-
use Minds\Core\Boost\V3\Utils\BoostConsoleUrlBuilder;
22-
use Minds\Core\Boost\V3\Utils\BoostReceiptUrlBuilder;
2323
use Minds\Core\EntitiesBuilder;
2424
use Minds\Core\EventStreams\ActionEvent;
2525
use Minds\Core\Log\Logger;
@@ -61,6 +61,8 @@ public function __construct(
6161
$this->receiptUrlBuilder ??= Di::_()->get(BoostReceiptUrlBuilder::class);
6262
$this->logger ??= Di::_()->get('Logger');
6363

64+
parent::__construct($this->manager);
65+
6466
$this->campaign = 'when';
6567
}
6668

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Minds\Core\Email\V2\Campaigns\Recurring\GiftCard;
5+
6+
use Minds\Core\Config\Config;
7+
use Minds\Core\Di\Di;
8+
use Minds\Core\Email\Campaigns\EmailCampaign;
9+
use Minds\Core\Email\Mailer;
10+
use Minds\Core\Email\V2\Campaigns\Recurring\GiftCard\GiftCardProducts\BoostCredit;
11+
use Minds\Core\Email\V2\Campaigns\Recurring\GiftCard\GiftCardProducts\GiftCardProductInterface;
12+
use Minds\Core\Email\V2\Common\Message;
13+
use Minds\Core\Email\V2\Common\Template;
14+
use Minds\Core\Email\V2\Partials\ActionButtonV2\ActionButtonV2;
15+
use Minds\Core\EntitiesBuilder;
16+
use Minds\Core\Log\Logger;
17+
use Minds\Core\Payments\GiftCards\Enums\GiftCardProductIdEnum;
18+
use Minds\Core\Payments\GiftCards\Models\GiftCard;
19+
use Minds\Entities\User;
20+
use Minds\Traits\MagicAttributes;
21+
use phpseclib3\Crypt\Random;
22+
use TheCodingMachine\GraphQLite\Exceptions\GraphQLException;
23+
24+
/**
25+
* @method self setGiftCard(GiftCard $giftCard)
26+
* @method self setTargetEmail(string $targetEmail)
27+
*/
28+
class Emailer extends EmailCampaign
29+
{
30+
use MagicAttributes;
31+
32+
private ?GiftCard $giftCard = null;
33+
34+
private ?string $targetEmail = null;
35+
36+
public function __construct(
37+
private readonly Template $template,
38+
private readonly Mailer $mailer,
39+
private readonly EntitiesBuilder $entitiesBuilder,
40+
private readonly Config $mindsConfig,
41+
private readonly Logger $logger,
42+
$manager = null,
43+
) {
44+
$this->manager = $manager ?? Di::_()->get('Email\Manager');
45+
parent::__construct($manager);
46+
47+
$this->campaign = "gift-card";
48+
}
49+
50+
/**
51+
* @inheritDoc
52+
*/
53+
public function send()
54+
{
55+
if (!$this->targetEmail && !$this->user?->email) {
56+
return;
57+
}
58+
59+
$sender = $this->entitiesBuilder->single($this->giftCard->issuedByGuid);
60+
if (!$sender) {
61+
return;
62+
}
63+
64+
$this->mailer->send($this->buildMessage($sender));
65+
66+
$this->logger->warning('Gift card email sent', [$this->mailer->getStats(), $this->mailer->getErrors()]);
67+
$this->saveCampaignLog();
68+
}
69+
70+
private function buildMessage(User $sender): ?Message
71+
{
72+
if (!$this->topic || !$this->giftCard) {
73+
return null;
74+
}
75+
76+
$bodyHandler = $this->getBodyHandler($this->giftCard->productId);
77+
$bodyHandler->setAmount($this->giftCard->amount);
78+
$bodyHandler->setSender($sender);
79+
80+
$bodyText = $bodyHandler->buildContent();
81+
82+
$this->template->setTemplate('default.v2.tpl');
83+
$this->template->setBody('./template.tpl');
84+
85+
$this->template->set('user', $this->user ?? null);
86+
$this->template->set('username', $this->user?->getUsername());
87+
$this->template->set('email', $this->user?->getEmail() ?? $this->targetEmail);
88+
$this->template->set('guid', $this->user?->getGuid());
89+
$this->template->set('campaign', $this->campaign);
90+
$this->template->set('topic', $this->topic);
91+
$this->template->set('tracking', $this->getTrackingQueryString());
92+
$this->template->set('title', '');
93+
$this->template->set('state', '');
94+
$this->template->set('preheader', "Claim your gift card");
95+
$this->template->set('bodyText', $bodyText);
96+
$this->template->set('headerText', "You received a gift");
97+
98+
$siteUrl = $this->mindsConfig->get('site_url') ?: 'https://www.minds.com/';
99+
$this->template->set('signupPath', $siteUrl . "register");
100+
101+
$actionButton = (new ActionButtonV2())
102+
->setLabel("Claim gift")
103+
->setPath($siteUrl . "gift-cards/claim/{$this->giftCard->claimCode}");
104+
105+
$this->template->set('actionButton', $actionButton->build());
106+
107+
return (new Message())
108+
->setTo($this->user ?? (new User())->setName("")->setEmail($this->targetEmail))
109+
->setMessageId(
110+
implode(
111+
'-',
112+
[
113+
$this->user?->getGuid() ?? Random::string(10),
114+
sha1($this->user?->getEmail() ?? $this->targetEmail),
115+
sha1($this->campaign . $this->topic . time())
116+
]
117+
)
118+
)
119+
->setFrom($sender->getEmail(), $sender->getName())
120+
->setSubject("You received a gift")
121+
->setHtml($this->template);
122+
}
123+
124+
/**
125+
* @param GiftCardProductIdEnum $productIdEnum
126+
* @return GiftCardProductInterface
127+
*/
128+
private function getBodyHandler(GiftCardProductIdEnum $productIdEnum): GiftCardProductInterface
129+
{
130+
return match ($productIdEnum) {
131+
GiftCardProductIdEnum::BOOST => new BoostCredit(),
132+
default => throw new GraphQLException("Invalid gift card product id: {$this->giftCard->productId}")
133+
};
134+
}
135+
136+
/**
137+
* @return string
138+
*/
139+
private function getTrackingQueryString(): string
140+
{
141+
return http_build_query(
142+
[
143+
'__e_ct_guid' => $this->user?->getGuid(),
144+
'__e_ct_email' => $this->user?->getEmail() ?? $this->targetEmail,
145+
'campaign' => 'when',
146+
'topic' => $this->topic,
147+
]
148+
);
149+
}
150+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Minds\Core\Email\V2\Campaigns\Recurring\GiftCard\GiftCardProducts;
5+
6+
use Minds\Entities\User;
7+
8+
class BoostCredit implements GiftCardProductInterface
9+
{
10+
private float $amount;
11+
private User $sender;
12+
13+
public function setAmount(float $amount): void
14+
{
15+
$this->amount = $amount;
16+
}
17+
18+
public function setSender(User $sender): void
19+
{
20+
$this->sender = $sender;
21+
}
22+
23+
public function buildContent(): string
24+
{
25+
return "You've been gifted <b>\${$this->amount} in Boost Credits</b> by <b>{$this->sender->getName()}</b> to use towards any future Boosts you make!";
26+
}
27+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Minds\Core\Email\V2\Campaigns\Recurring\GiftCard\GiftCardProducts;
4+
5+
use Minds\Entities\User;
6+
7+
interface GiftCardProductInterface
8+
{
9+
public function setAmount(float $amount): void;
10+
11+
public function setSender(User $sender): void;
12+
13+
public function buildContent(): string;
14+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<h1 <?= $emailStylesV2->getStyles(['m-mainContent__h1']) ?>>
2+
<?php echo $vars['headerText']; ?>
3+
</h1>
4+
5+
<!--BODY SUBJECT TEXT-->
6+
<?php if(isset($vars['bodySubjectText'])){ ?>
7+
<p <?= $emailStylesV2->getStyles(['m-mainContent__paragraphSubject']) ?> >
8+
<?php echo $vars['bodySubjectText']; ?></p>
9+
<?php } ?>
10+
11+
<!--BODY TEXT-->
12+
<p <?= $emailStylesV2->getStyles(['m-mainContent__paragraph']) ?> >
13+
<?php echo $vars['bodyText']; ?>
14+
</p>
15+
16+
<!--ACTION BUTTON-->
17+
<?php if(isset($vars['actionButton'])){ ?>
18+
<?php echo $vars['actionButton']; ?>
19+
<?php } ?>
20+
21+
<p <?= $emailStylesV2->getStyles(['m-mainContent__signup_paragraph']) ?>>
22+
Don't have a Minds account?<br>
23+
<a <?= $emailStylesV2->getStyles(['m-mainContent__signup_paragraph--link']) ?> href="<?php echo $vars['signupPath']; ?>">Sign up</a> to claim
24+
</p>
25+
26+
<!-- ADD'L LINK (OPTIONAL) -->
27+
<?php if(isset($vars['additionalCtaPath']) && isset($vars['additionalCtaText'])){ ?>
28+
<a <?= $emailStylesV2->getStyles(['m-mainContent__standaloneLink']) ?> href="<?php echo $vars['additionalCtaPath']; ?>">
29+
<?php echo $vars['additionalCtaText']; ?>
30+
</a>
31+
<?php } ?>

0 commit comments

Comments
 (0)