From 5c29697dab51bcd2ecbf794c5f64dd28cf8a3627 Mon Sep 17 00:00:00 2001 From: Jelle Besseling Date: Sun, 17 Nov 2024 16:38:56 +0100 Subject: [PATCH 1/2] Allow usage of member number in subscription message --- config/instances/ds.yaml | 2 + config/instances/rood.yaml | 2 + .../Admin/MembershipApplicationCrud.php | 31 ++++----- src/Controller/ContributionController.php | 50 ++++++-------- src/Controller/MemberController.php | 2 +- src/Service/SubscriptionSetupService.php | 68 +++++++++++++++++++ 6 files changed, 107 insertions(+), 48 deletions(-) create mode 100644 src/Service/SubscriptionSetupService.php diff --git a/config/instances/ds.yaml b/config/instances/ds.yaml index 6dc77c5..d8d4cdd 100644 --- a/config/instances/ds.yaml +++ b/config/instances/ds.yaml @@ -7,3 +7,5 @@ contribution: signup: min_age: 16 max_age: null + +mollie_payment_description: "Contributiebetaling {{organisation_name}}, voor lidnummer {{member_number}}" diff --git a/config/instances/rood.yaml b/config/instances/rood.yaml index 07e3422..460fe2d 100644 --- a/config/instances/rood.yaml +++ b/config/instances/rood.yaml @@ -11,3 +11,5 @@ contribution: signup: min_age: 14 max_age: 27 + +mollie_payment_description: "Contributiebetaling {{organisation_name}}, voor lidnummer {{member_number}}" diff --git a/src/Controller/Admin/MembershipApplicationCrud.php b/src/Controller/Admin/MembershipApplicationCrud.php index eb7ede0..bc32e23 100644 --- a/src/Controller/Admin/MembershipApplicationCrud.php +++ b/src/Controller/Admin/MembershipApplicationCrud.php @@ -2,6 +2,7 @@ namespace App\Controller\Admin; +use App\Service\SubscriptionSetupService; use Doctrine\ORM\QueryBuilder; use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection; @@ -31,7 +32,11 @@ class MembershipApplicationCrud extends AbstractCrudController private MailerInterface $mailer; private MollieApiClient $mollieApiClient; - public function __construct(MailerInterface $mailer, MollieApiClient $mollieApiClient) + public function __construct( + MailerInterface $mailer, + MollieApiClient $mollieApiClient, + private readonly SubscriptionSetupService $subscriptionService, + ) { $this->mailer = $mailer; $this->mollieApiClient = $mollieApiClient; @@ -90,6 +95,7 @@ public function acceptApplication(AdminContext $context) $organizationName = $this->getParameter('app.organizationName'); $mailer = $this->mailer; + /** @var MembershipApplication $application */ $application = $context->getEntity()->getInstance(); $mollieIntervals = [ @@ -106,32 +112,25 @@ public function acceptApplication(AdminContext $context) $startDate = new DateTime(); $startDate->setDate(date('Y'), floor(date('m') / 3) * 3, 1); $startDate->add(new DateInterval($dateTimeIntervals[$application->getContributionPeriod()])); - $subscriptionId = null; + + $member = $application->createMember(null); + $member->generateNewPasswordToken(); + $em = $this->getDoctrine()->getManager(); + $em->persist($member); + $em->flush(); try { $customer = $this->mollieApiClient->customers->get($application->getMollieCustomerId()); - $subscription = $customer->createSubscription([ - 'amount' => [ - 'currency' => 'EUR', - 'value' => number_format($application->getContributionPerPeriodInEuros(), 2, '.', '') - ], - 'interval' => $mollieIntervals[$application->getContributionPeriod()], - 'description' => $this->getParameter('mollie_payment_description'), - 'startDate' => $startDate->format('Y-m-d'), - 'webhookUrl' => $this->generateUrl('member_contribution_mollie_webhook', [], UrlGeneratorInterface::ABSOLUTE_URL) - ]); + $subscription = $this->subscriptionService->createSubscription($member, $customer); $subscriptionId = $subscription->id; + $member->setMollieSubscriptionId($subscriptionId); } catch (ApiException $e) { // De subscription moet later nog gedaan worden door het lid zelf $this->addFlash("warning", "Het net geaccepteerde lid heeft nog geen automatisch incasso. Het nieuwe lid kan dit alleen zelf instellen."); } - $member = $application->createMember($subscriptionId); - $member->generateNewPasswordToken(); - - $em = $this->getDoctrine()->getManager(); $em->persist($member); $em->remove($application); $em->flush(); diff --git a/src/Controller/ContributionController.php b/src/Controller/ContributionController.php index 853841b..41d727a 100644 --- a/src/Controller/ContributionController.php +++ b/src/Controller/ContributionController.php @@ -2,6 +2,7 @@ namespace App\Controller; +use App\Service\SubscriptionSetupService; use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\{ Response, Request }; @@ -21,6 +22,12 @@ class ContributionController extends AbstractController { + public function __construct( + private readonly SubscriptionSetupService $subscriptionService, + ) + { + } + /** * @Route("/contributie-instellingen", name="member_contribution_preferences") */ @@ -134,7 +141,9 @@ public function payContribution(Request $request, MollieApiClient $mollieApiClie if ($automaticCollection) { return $this->render('user/contribution/first-payment.html.twig', [ - 'checkoutUrl' => $molliePayment->getCheckoutUrl() + 'checkoutUrl' => $molliePayment->getCheckoutUrl(), + // The user is currently setting up contribution, so setting this false disables the contribution nagbar + 'contributionEnabled' => false, ]); } @@ -176,7 +185,7 @@ public function webhook(Request $request, MollieApiClient $mollieApiClient): Res $contributionPayment->setStatus(ContributionPayment::STATUS_PAID); if ($member->getCreateSubscriptionAfterPayment()) { - $this->setupSubscription($member, $customer); + $this->subscriptionService->createSubscription($member, $customer); $member->setCreateSubscriptionAfterPayment(false); } break; @@ -225,6 +234,7 @@ public function automaticCollection(Request $request, LoggerInterface $logger): */ public function enableAutomaticCollection(Request $request, MollieApiClient $mollieApiClient): Response { $em = $this->getDoctrine()->getManager(); + /** @var Member $member */ $member = $this->getUser(); // Already has subscription @@ -243,7 +253,7 @@ public function enableAutomaticCollection(Request $request, MollieApiClient $mol } // 3. If a mandate does exist, set up subscription - $this->setupSubscription($member, $customer); + $this->subscriptionService->createSubscription($member, $customer); // 4. Show automatic collection enabled screen return $this->redirectToRoute('member_contribution_automatic_collection'); @@ -445,23 +455,23 @@ private function getOrCreateMollieCustomer(MollieApiClient $mollieApiClient) { return $customer; } - private function createContributionPayment(int $contributionPeriod, float $contributionAmount, $molliePayment) { + private function createContributionPayment(int $contributionPeriod, float $contributionAmount, $molliePayment) + { // Create contribution payment $contributionPayment = new ContributionPayment(); $contributionPayment->setAmountInCents(round($contributionAmount * 100)); $contributionPayment->setStatus(ContributionPayment::STATUS_PENDING); $contributionPayment->setPaymentTime(new DateTime); - if ($molliePayment !== null) - { + if ($molliePayment !== null) { $contributionPayment->setMolliePaymentId($molliePayment->id); } // Set correct year and period information - $contributionPayment->setPeriodYear((int) date('Y')); + $contributionPayment->setPeriodYear((int)date('Y')); - $month = (int) date('m'); - switch($contributionPeriod) { + $month = (int)date('m'); + switch ($contributionPeriod) { case Member::PERIOD_MONTHLY: $contributionPayment->setPeriodMonthStart($month); $contributionPayment->setPeriodMonthEnd($month); @@ -479,26 +489,4 @@ private function createContributionPayment(int $contributionPeriod, float $contr return $contributionPayment; } - - private function setupSubscription(Member $member, $customer) { - $startDate = DateTime::createFromFormat('Y-m-d', date('Y-'). (ceil(date('m') / 3) * 3 + 1). '-1'); - - $subscription = $customer->createSubscription([ - 'amount' => [ - 'currency' => 'EUR', - 'value' => number_format($member->getContributionPerPeriodInEuros(), 2, '.', '') - ], - 'interval' => [ - Member::PERIOD_MONTHLY => '1 month', - Member::PERIOD_QUARTERLY => '3 months', - Member::PERIOD_ANNUALLY => '1 year' - ][$member->getContributionPeriod()], - 'description' => $this->getParameter('mollie_payment_description'), - 'startDate' => $startDate->format('Y-m-d'), - 'webhookUrl' => $this->generateUrl('member_contribution_mollie_webhook', [], UrlGeneratorInterface::ABSOLUTE_URL) - ]); - $member->setMollieSubscriptionId($subscription->id); - $this->getDoctrine()->getManager()->flush(); - } - } diff --git a/src/Controller/MemberController.php b/src/Controller/MemberController.php index b4e8b6e..6d2d148 100644 --- a/src/Controller/MemberController.php +++ b/src/Controller/MemberController.php @@ -333,7 +333,7 @@ public function details(Request $request, UserPasswordEncoderInterface $password $member = $this->getUser(); if (!$member->getAcceptUsePersonalInformation()) return $this->memberAcceptPersonalDetails($request); - $contributionEnabled = $this->getParameter('app.contributionEnabled'); + $contributionEnabled = $this->getParameter('app.contributionEnabled'); $form = $this->createForm(MemberDetailsType::class, $member); $revision = new MemberDetailsRevision($member, true); $success = false; diff --git a/src/Service/SubscriptionSetupService.php b/src/Service/SubscriptionSetupService.php new file mode 100644 index 0000000..6988e23 --- /dev/null +++ b/src/Service/SubscriptionSetupService.php @@ -0,0 +1,68 @@ + '1 month', + Member::PERIOD_QUARTERLY => '3 months', + Member::PERIOD_ANNUALLY => '1 year' + ]; + $dateTimeIntervals = [ + Member::PERIOD_MONTHLY => 'P1M', + Member::PERIOD_QUARTERLY => 'P3M', + Member::PERIOD_ANNUALLY => 'P1Y' + ]; + + $startDate = new DateTime(); + $startDate->setDate((int)date('Y'), (int)floor(date('m') / 3) * 3, 1); + $startDate->add(new \DateInterval($dateTimeIntervals[$member->getContributionPeriod()])); + + $projectRoot = $this->params->get('kernel.project_dir'); + $org_config = Yaml::parseFile($projectRoot . '/config/instances/' . $this->params->get('app.organizationID') . '.yaml'); + + $description = $org_config['mollie_payment_description']; + $description = str_replace('{{organisation_name}}', $this->params->get("app.organizationName"), $description); + $description = str_replace('{{member_number}}', (string)$member->getId(), $description); + + $subscription = $customer->createSubscription([ + 'amount' => [ + 'currency' => 'EUR', + 'value' => number_format($member->getContributionPerPeriodInEuros(), 2, '.', '') + ], + 'interval' => $mollieIntervals[$member->getContributionPeriod()], + 'description' => $description, + 'startDate' => $startDate->format('Y-m-d'), + 'webhookUrl' => $this->router->generate('member_contribution_mollie_webhook', [], UrlGeneratorInterface::ABSOLUTE_URL) + ]); + $member->setMollieSubscriptionId($subscription->id); + $this->doctrine->getManager()->flush(); + return $subscription; + } +} From 696ac58abb32c5ea69a1cf3c0ad6e38ac0cc9354 Mon Sep 17 00:00:00 2001 From: Jelle Besseling Date: Sun, 17 Nov 2024 17:35:55 +0100 Subject: [PATCH 2/2] Add migration command for new descriptions --- .../MigrateSubscriptionToNewMessage.php | 87 +++++++++++++++++++ .../Admin/MembershipApplicationCrud.php | 15 ---- src/Service/SubscriptionSetupService.php | 17 ++-- 3 files changed, 98 insertions(+), 21 deletions(-) create mode 100644 src/Command/MigrateSubscriptionToNewMessage.php diff --git a/src/Command/MigrateSubscriptionToNewMessage.php b/src/Command/MigrateSubscriptionToNewMessage.php new file mode 100644 index 0000000..c66a2af --- /dev/null +++ b/src/Command/MigrateSubscriptionToNewMessage.php @@ -0,0 +1,87 @@ +writeln('Updating Mollie subscription to new message'); + + try { + $this->confirmChangeCount($input, $output); + } catch (\Exception $e) { + $output->writeln($e->getMessage()); + return Command::FAILURE; + } + + $memberRepository = $this->entityManager->getRepository(Member::class); + + $allSubscriptions = $this->mollieApiClient->subscriptions->iterator(); + foreach ($allSubscriptions as $subscription) { + /** @var Subscription $subscription */ + if (!$subscription->isActive()) { + continue; + } + $output->writeln('Updating subscription ' . $subscription->description); + /** @var Member $member */ + $members = $memberRepository->findBy(['mollieSubscriptionId' => $subscription->id]); + if (count($members) === 0) { + $output->writeln('Could not find member for subscription ' . $subscription->id . '! (skipping)'); + continue; + } + $member = $members[0]; + $newDescription = $this->subscriptionService->generateDescription($member); + $subscription->description = $newDescription; + try { + $subscription->update(); + } catch (ApiException $exception) { + $output->writeln('' . $exception->getMessage() . ''); + } + } + + return Command::SUCCESS; + } + + /** + * @throws \Exception + */ + private function confirmChangeCount(InputInterface $input, OutputInterface $output): void + { + $allSubscriptions = $this->mollieApiClient->subscriptions->iterator(); + $amount = $allSubscriptions->count(); + $helper = $this->getHelper('question'); + $question = new Question("You are about to change $amount descriptions, to continue confirm the amount: ", false); + $answer = (int)$helper->ask($input, $output, $question); + if ($answer === $amount) { + return; + } + throw new \Exception('Did not confirm'); + } +} diff --git a/src/Controller/Admin/MembershipApplicationCrud.php b/src/Controller/Admin/MembershipApplicationCrud.php index bc32e23..d36a316 100644 --- a/src/Controller/Admin/MembershipApplicationCrud.php +++ b/src/Controller/Admin/MembershipApplicationCrud.php @@ -98,21 +98,6 @@ public function acceptApplication(AdminContext $context) /** @var MembershipApplication $application */ $application = $context->getEntity()->getInstance(); - $mollieIntervals = [ - Member::PERIOD_MONTHLY => '1 month', - Member::PERIOD_QUARTERLY => '3 months', - Member::PERIOD_ANNUALLY => '1 year' - ]; - $dateTimeIntervals = [ - Member::PERIOD_MONTHLY => 'P1M', - Member::PERIOD_QUARTERLY => 'P3M', - Member::PERIOD_ANNUALLY => 'P1Y' - ]; - - $startDate = new DateTime(); - $startDate->setDate(date('Y'), floor(date('m') / 3) * 3, 1); - $startDate->add(new DateInterval($dateTimeIntervals[$application->getContributionPeriod()])); - $member = $application->createMember(null); $member->generateNewPasswordToken(); $em = $this->getDoctrine()->getManager(); diff --git a/src/Service/SubscriptionSetupService.php b/src/Service/SubscriptionSetupService.php index 6988e23..f1d1829 100644 --- a/src/Service/SubscriptionSetupService.php +++ b/src/Service/SubscriptionSetupService.php @@ -24,6 +24,16 @@ public function __construct( { } + public function generateDescription(Member $member): string + { + $projectRoot = $this->params->get('kernel.project_dir'); + $org_config = Yaml::parseFile($projectRoot . '/config/instances/' . $this->params->get('app.organizationID') . '.yaml'); + + $description = $org_config['mollie_payment_description']; + $description = str_replace('{{organisation_name}}', $this->params->get("app.organizationName"), $description); + return str_replace('{{member_number}}', (string)$member->getId(), $description); + } + /** * @throws ApiException */ @@ -44,12 +54,7 @@ public function createSubscription(Member $member, Customer $customer): Subscrip $startDate->setDate((int)date('Y'), (int)floor(date('m') / 3) * 3, 1); $startDate->add(new \DateInterval($dateTimeIntervals[$member->getContributionPeriod()])); - $projectRoot = $this->params->get('kernel.project_dir'); - $org_config = Yaml::parseFile($projectRoot . '/config/instances/' . $this->params->get('app.organizationID') . '.yaml'); - - $description = $org_config['mollie_payment_description']; - $description = str_replace('{{organisation_name}}', $this->params->get("app.organizationName"), $description); - $description = str_replace('{{member_number}}', (string)$member->getId(), $description); + $description = $this->generateDescription($member); $subscription = $customer->createSubscription([ 'amount' => [