Skip to content

Commit

Permalink
Merge pull request #115 from fhervieux/ignored-shops
Browse files Browse the repository at this point in the history
Enable ignored Mirakl shops
  • Loading branch information
millin-stripe authored Nov 3, 2022
2 parents d245342 + 546a0f8 commit 5c02f5d
Show file tree
Hide file tree
Showing 11 changed files with 191 additions and 3 deletions.
3 changes: 3 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ parameters:
env(MAIL_ON_NOTIFICATION_ENDPOINT_DOWN_COOLDOWN): 10
env(STRIPE_PREFILL_ONBOARDING): false
env(MIRAKL_CUSTOM_FIELD_CODE): "stripe-url"
env(MIRAKL_IGNORED_SHOP_FIELD_CODE): "stripe-ignored"
env(ENABLE_SERVICE_PAYMENT_SPLIT): false
env(ENABLE_SERVICE_PAYMENT_REFUND): false
env(ENABLE_SELLER_ONBOARDING): true
Expand Down Expand Up @@ -45,6 +46,7 @@ parameters:
app.mirakl.api_key: "%env(MIRAKL_API_KEY)%"
app.mirakl.host_name: "%env(MIRAKL_HOST_NAME)%"
app.mirakl.stripe_custom_field_code: "%env(MIRAKL_CUSTOM_FIELD_CODE)%"
app.mirakl.stripe_ignored_shop_field_code: "%env(MIRAKL_IGNORED_SHOP_FIELD_CODE)%"
app.redirect.onboarding: "%env(default:default_redirect_onboarding:REDIRECT_ONBOARDING)%"
app.operator.notification_url: "%env(OPERATOR_NOTIFICATION_URL)%"
app.mailer.technical: "%env(TECHNICAL_ALERT_EMAIL)%"
Expand All @@ -66,6 +68,7 @@ services:
$miraklApiKey: "%app.mirakl.api_key%"
$miraklHostName: "%app.mirakl.host_name%"
$customFieldCode: "%app.mirakl.stripe_custom_field_code%"
$ignoredShopFieldCode: "%app.mirakl.stripe_ignored_shop_field_code%"
$enableProductPaymentSplit: "%app.workflow.enable_product_payment_split%"
$enableServicePaymentSplit: "%app.workflow.enable_service_payment_split%"
$enableProductPaymentRefund: "%app.workflow.enable_product_payment_refund%"
Expand Down
1 change: 1 addition & 0 deletions config/services_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ services:
public: true
bind:
$customFieldCode: "%app.mirakl.stripe_custom_field_code%"
$ignoredShopFieldCode: "%app.mirakl.stripe_ignored_shop_field_code%"

App\Service\MiraklClient:
class: App\Service\MiraklClient
Expand Down
6 changes: 6 additions & 0 deletions src/Command/SellerOnboardingCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ private function processUpdatedShops()
continue;
}

$ignoredShop = $this->sellerOnboardingService->isShopIgnored($shop);
if ($accountMapping->getIgnored() !== $ignoredShop) {
$this->logger->info("Shop $shopId is now ignored=" . var_export($ignoredShop, true));
$this->sellerOnboardingService->updateAccountMappingIgnored($accountMapping, $ignoredShop);
}

try {
// Ignore if custom field already has a value other than the oauth URL (for backward compatibility)
$customFieldValue = $this->sellerOnboardingService->getCustomFieldValue($shop);
Expand Down
17 changes: 17 additions & 0 deletions src/Entity/AccountMapping.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ class AccountMapping
*/
private $payinEnabled = false;

/**
* @ORM\Column(type="boolean", options={"default" : false})
*/
private $ignored = false;

/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
Expand Down Expand Up @@ -134,6 +139,18 @@ public function setPayinEnabled(bool $payinEnabled): self
return $this;
}

public function getIgnored(): ?bool
{
return $this->ignored;
}

public function setIgnored(bool $ignored): self
{
$this->ignored = $ignored;

return $this;
}

public function getDisabledReason(): ?string
{
return $this->disabledReason;
Expand Down
2 changes: 2 additions & 0 deletions src/Entity/StripeTransfer.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class StripeTransfer
public const TRANSFER_PENDING = 'TRANSFER_PENDING';
public const TRANSFER_FAILED = 'TRANSFER_FAILED';
public const TRANSFER_CREATED = 'TRANSFER_CREATED';
public const TRANSFER_IGNORED = 'TRANSFER_IGNORED';

// Transfer status reasons: on hold
public const TRANSFER_STATUS_REASON_SHOP_NOT_READY = 'Cannot find Stripe account for shop ID %s';
Expand Down Expand Up @@ -139,6 +140,7 @@ public static function getAvailableStatus(): array
self::TRANSFER_FAILED,
self::TRANSFER_ON_HOLD,
self::TRANSFER_ABORTED,
self::TRANSFER_IGNORED,
];
}

Expand Down
21 changes: 21 additions & 0 deletions src/Factory/StripeTransferFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ public function updateFromOrder(StripeTransfer $transfer, MiraklOrder $order, Mi
return $this->putTransferOnHold($transfer, $e->getMessage());
}

if ($accountMapping->getIgnored()) {
$shopId = $accountMapping->getMiraklShopId();
return $this->ignoreTransfer($transfer, "Shop $shopId is ignored");
}

// Order must not be refused or canceled
if ($order->isAborted()) {
return $this->abortTransfer(
Expand Down Expand Up @@ -538,6 +543,22 @@ private function abortTransfer(StripeTransfer $transfer, string $reason): Stripe
->setStatusReason(substr($reason, 0, 1024));
}

/**
* @param StripeTransfer $transfer
* @return StripeTransfer
*/
private function ignoreTransfer(StripeTransfer $transfer, string $reason): StripeTransfer
{
$this->logger->info(
'Transfer ignored: ' . $reason,
['order_id' => $transfer->getMiraklId()]
);

return $transfer
->setStatus(StripeTransfer::TRANSFER_IGNORED)
->setStatusReason(substr($reason, 0, 1024));
}

/**
* @param StripeTransfer $transfer
* @return StripeTransfer
Expand Down
31 changes: 31 additions & 0 deletions src/Migrations/Version20221031140741.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

final class Version20221031140741 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add AccountMapping ignored field';
}

public function up(Schema $schema): void
{
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');

$this->addSql('ALTER TABLE account_mapping ADD ignored BOOLEAN DEFAULT \'false\' NOT NULL');
}

public function down(Schema $schema): void
{
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');

$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE account_mapping DROP ignored');
}
}
28 changes: 27 additions & 1 deletion src/Service/SellerOnboardingService.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,20 @@ class SellerOnboardingService
*/
private $customFieldCode;

/**
* @var string
*/
private $ignoredShopFieldCode;

public function __construct(
AccountMappingRepository $accountMappingRepository,
MiraklClient $miraklClient,
StripeClient $stripeClient,
RouterInterface $router,
string $redirectOnboarding,
bool $stripePrefillOnboarding,
string $customFieldCode
string $customFieldCode,
string $ignoredShopFieldCode
) {
$this->accountMappingRepository = $accountMappingRepository;
$this->miraklClient = $miraklClient;
Expand All @@ -64,6 +70,7 @@ public function __construct(
$this->redirectOnboarding = $redirectOnboarding;
$this->stripePrefillOnboarding = $stripePrefillOnboarding;
$this->customFieldCode = $customFieldCode;
$this->ignoredShopFieldCode = $ignoredShopFieldCode;
}

/**
Expand Down Expand Up @@ -92,6 +99,16 @@ public function getAccountMappingFromShop(MiraklShop $shop): AccountMapping
return $accountMapping;
}

/**
* @param AccountMapping $accountMapping
* @param bool $ignored
*/
public function updateAccountMappingIgnored(AccountMapping $accountMapping, bool $ignored): void
{
$accountMapping->setIgnored($ignored);
$this->accountMappingRepository->persistAndFlush($accountMapping);
}

/**
* @param MiraklShop $shop
* @return Account
Expand Down Expand Up @@ -125,6 +142,15 @@ public function getCustomFieldValue(MiraklShop $shop): ?string
return $shop->getCustomFieldValue($this->customFieldCode);
}

/**
* @param MiraklShop $shop
* @return bool True if the field is set and the shop ignored, false otherwise.
*/
public function isShopIgnored(MiraklShop $shop): bool
{
return $shop->getCustomFieldValue($this->ignoredShopFieldCode) === "true";
}

/**
* @param int $shopId
* @param AccountMapping $accountMapping
Expand Down
19 changes: 19 additions & 0 deletions tests/Command/SellerOnboardingCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,23 @@ public function testShopUpdateDateCheckpoint()
$this->executeCommand();
$this->assertEquals(MiraklMock::SHOP_DATE_1_EXISTING_WITH_OAUTH_URL, $this->configService->getSellerOnboardingCheckpoint());
}

public function testIgnoredNewShop()
{
$this->deleteAllAccountMappingsFromRepository();
$this->configService->setSellerOnboardingCheckpoint(MiraklMock::SHOP_DATE_1_NEW_IGNORED);
$this->executeCommand();
$this->assertCount(1, $this->getAccountMappingsFromRepository());
$this->assertEquals(true, current($this->getAccountMappingsFromRepository())->getIgnored());
}

public function testIgnoredExistingShop()
{
$this->deleteAllAccountMappingsFromRepository();
$this->mockAccountMapping(MiraklMock::SHOP_EXISTING_IGNORED);
$this->configService->setSellerOnboardingCheckpoint(MiraklMock::SHOP_DATE_1_EXISTING_IGNORED);
$this->executeCommand();
$this->assertCount(1, $this->getAccountMappingsFromRepository());
$this->assertEquals(true, current($this->getAccountMappingsFromRepository())->getIgnored());
}
}
36 changes: 35 additions & 1 deletion tests/Factory/StripeTransferFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@ protected function setUp(): void
->getRepository(StripeRefund::class);
$this->stripeTransferRepository = $container->get('doctrine')
->getRepository(StripeTransfer::class);
$this->accountMappingRepository = $container->get('doctrine')
->getRepository(AccountMapping::class);

$this->stripeTransferFactory = new StripeTransferFactory(
$container->get('doctrine')->getRepository(AccountMapping::class),
$this->accountMappingRepository,
$this->paymentMappingRepository,
$this->stripeRefundRepository,
$this->stripeTransferRepository,
Expand Down Expand Up @@ -262,6 +264,22 @@ public function testProductOrderInvalidShop()
$this->assertNotNull($transfer->getStatusReason());
}

public function testProductOrderIgnoredShop()
{
$accountMapping = new AccountMapping();
$accountMapping->setMiraklShopId(MiraklMock::SHOP_EXISTING_IGNORED);
$accountMapping->setStripeAccountId(StripeMock::ACCOUNT_NEW);
$accountMapping->setIgnored(true);
$this->accountMappingRepository->persistAndFlush($accountMapping);

$transfer = $this->stripeTransferFactory->createFromOrder(
current($this->miraklClient->listProductOrdersById([
MiraklMock::ORDER_IGNORED_SHOP
]))
);
$this->assertEquals(StripeTransfer::TRANSFER_IGNORED, $transfer->getStatus());
}

public function testProductOrderInvalidAmount()
{
$transfer = $this->stripeTransferFactory->createFromOrder(
Expand Down Expand Up @@ -468,6 +486,22 @@ public function testServiceOrderInvalidShop()
$this->assertNotNull($transfer->getStatusReason());
}

public function testServiceOrderIgnoredShop()
{
$accountMapping = new AccountMapping();
$accountMapping->setMiraklShopId(MiraklMock::SHOP_EXISTING_IGNORED);
$accountMapping->setStripeAccountId(StripeMock::ACCOUNT_NEW);
$accountMapping->setIgnored(true);
$this->accountMappingRepository->persistAndFlush($accountMapping);

$transfer = $this->stripeTransferFactory->createFromOrder(
current($this->miraklClient->listServiceOrdersById([
MiraklMock::ORDER_IGNORED_SHOP
]))
);
$this->assertEquals(StripeTransfer::TRANSFER_IGNORED, $transfer->getStatus());
}

public function testServiceOrderInvalidAmount()
{
$transfer = $this->stripeTransferFactory->createFromOrder(
Expand Down
30 changes: 29 additions & 1 deletion tests/MiraklMockedHttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class MiraklMockedHttpClient extends MockHttpClient
public const ORDER_WITH_TRANSACTION_NUMBER = 'order_with_transaction_number';
public const ORDER_INVALID_AMOUNT = 'order_invalid_amount';
public const ORDER_INVALID_SHOP = 'order_invalid_shop';
public const ORDER_IGNORED_SHOP = 'order_ignored_shop';
public const ORDER_AMOUNT_NO_COMMISSION = 'order_no_commission';
public const ORDER_AMOUNT_NO_TAX = 'order_no_tax';
public const ORDER_AMOUNT_PARTIAL_TAX = 'order_partial_tax';
Expand Down Expand Up @@ -73,13 +74,17 @@ class MiraklMockedHttpClient extends MockHttpClient
public const SHOP_NEW = 299;
public const SHOP_STRIPE_ERROR = 399;
public const SHOP_MIRAKL_ERROR = 499;
public const SHOP_NEW_IGNORED = 500;
public const SHOP_EXISTING_IGNORED = 501;
public const SHOP_DATE_1_NEW = '2019-01-01T00:00:00+0100';
public const SHOP_DATE_1_STRIPE_ERROR = '2019-01-02T00:00:00+0100';
public const SHOP_DATE_1_MIRAKL_ERROR = '2019-01-03T00:00:00+0100';
public const SHOP_DATE_1_EXISTING_WITHOUT_URL = '2019-01-04T00:00:00+0100';
public const SHOP_DATE_1_EXISTING_WITH_URL = '2019-01-05T00:00:00+0100';
public const SHOP_DATE_1_EXISTING_WITH_OAUTH_URL = '2019-01-06T00:00:00+0100';
public const SHOP_DATE_MULTIPLE_UNSORTED = '2019-01-07T00:00:00+0100';
public const SHOP_DATE_1_NEW_IGNORED = '2019-01-08T00:00:00+0100';
public const SHOP_DATE_1_EXISTING_IGNORED = '2019-01-09T00:00:00+0100';

public const INVOICE_BASIC = 1;
public const INVOICE_INVALID_AMOUNT = 2;
Expand Down Expand Up @@ -110,10 +115,12 @@ class MiraklMockedHttpClient extends MockHttpClient
public const INVOICE_DATE_14_NEW_INVOICES_ALL_READY_END_ID = 1099;

private $customFieldCode;
private $ignoredShopFieldCode;

public function __construct(string $customFieldCode)
public function __construct(string $customFieldCode, string $ignoredShopFieldCode)
{
$this->customFieldCode = $customFieldCode;
$this->ignoredShopFieldCode = $ignoredShopFieldCode;

$responseFactory = function ($method, $url, $options) {
$path = parse_url($url, PHP_URL_PATH);
Expand Down Expand Up @@ -520,6 +527,13 @@ private function mockOrdersById($isService, $orderIds, $startDate = null)
$order['total_commission'] = 100;
}
break;
case self::ORDER_IGNORED_SHOP:
if ($isService) {
$order['shop']['id'] = self::SHOP_EXISTING_IGNORED;
} else {
$order['shop_id'] = self::SHOP_EXISTING_IGNORED;
}
break;
case self::ORDER_WITH_TRANSACTION_NUMBER:
if (!$isService) {
$order['transaction_number'] = StripeMock::CHARGE_BASIC;
Expand Down Expand Up @@ -875,6 +889,12 @@ private function mockShopsByDate($date, $page)
break;
case self::SHOP_DATE_MULTIPLE_UNSORTED:
$shops = $this->mockShopsById([self::SHOP_WITH_OAUTH_URL, self::SHOP_WITH_URL]);
break;
case self::SHOP_DATE_1_NEW_IGNORED:
$shops = $this->mockShopsById([self::SHOP_NEW_IGNORED]);
break;
case self::SHOP_DATE_1_EXISTING_IGNORED:
$shops = $this->mockShopsById([self::SHOP_EXISTING_IGNORED]);
break;
default:
$shops = [];
Expand Down Expand Up @@ -911,6 +931,14 @@ private function mockShopsById($shopIds)
$shop['last_updated_date'] = self::SHOP_DATE_1_EXISTING_WITH_OAUTH_URL;
$shops[] = $shop;
break;
case self::SHOP_NEW_IGNORED:
case self::SHOP_EXISTING_IGNORED:
$shop['shop_additional_fields'][] = [
'code' => $this->ignoredShopFieldCode,
'value' => 'true',
];
$shops[] = $shop;
break;
case self::SHOP_INVALID:
default:
// Don't return anything
Expand Down

0 comments on commit 5c02f5d

Please sign in to comment.