Skip to content
advanced Phase 87 · Integration Patterns

CRM Integration

CRM integration - Salesforce, HubSpot integration, customer data synchronization patterns

45m
0 problems
Topic Progress 0%

CRM Integration Architecture

CRM Integration Overview

┌─────────────┐     ┌──────────────────┐     ┌─────────────┐
│   Magento   │◄───►│  CRM Connector   │◄───►│ Salesforce/ │
│   2 Store   │     │  (Sync Engine)   │     │  HubSpot    │
└─────────────┘     └──────────────────┘     └─────────────┘
       │                    │                       │
  Customer Data        Contact Sync            CRM Records
  Order Data           Lead Mapping            Opportunities
  Cart Data            Opportunity Sync        Campaigns

CRM Adapter Interface

namespace Vendor\Integration\Model\Crm;

interface CrmAdapterInterface
{
    /**
     * Sync customer to CRM
     */
    public function syncCustomer(CustomerInterface $customer): CrmResult;

    /**
     * Sync order as opportunity
     */
    public function syncOrderAsOpportunity(OrderInterface $order): CrmResult;

    /**
     * Pull CRM contact data
     */
    public function pullContact(string $crmId): ContactData;

    /**
     * Sync abandoned cart as lead
     */
    public function syncCartAsLead(CartInterface $cart): CrmResult;
}

Salesforce Adapter

namespace Vendor\Integration\Model\Crm\Salesforce;

class SalesforceAdapter implements CrmAdapterInterface
{
    private SalesforceClient $client;
    private FieldMapperInterface $fieldMapper;

    public function syncCustomer(CustomerInterface $customer): CrmResult
    {
        $mappedData = $this->fieldMapper->mapCustomer($customer);

        $response = $this->client->upsert('Contact', [
            'ExternalId__c' => $customer->getId(),
            'FirstName' => $customer->getFirstname(),
            'LastName' => $customer->getLastname(),
            'Email' => $customer->getEmail(),
            'Phone' => $customer->getPhone(),
            'MailingStreet' => $customer->getStreetFull(),
            'MailingCity' => $customer->getCity(),
            'MailingState' => $customer->getRegion()->getRegionCode(),
            'MailingPostalCode' => $customer->getPostcode(),
            'MailingCountry' => $customer->getCountryId(),
        ]);

        return new CrmResult([
            'success' => true,
            'crm_id' => $response['id'],
            'action' => $response['created'] ? 'created' : 'updated',
        ]);
    }
}

Customer Data Sync

Sync Strategy

namespace Vendor\Integration\Model\Crm\Sync;

class CustomerSyncStrategy
{
    private CrmAdapterInterface $crmAdapter;
    private CustomerRepositoryInterface $customerRepo;
    private SyncLogInterface $syncLog;

    public function syncAllCustomers(int $pageSize = 100): SyncReport
    {
        $report = new SyncReport();
        $page = 1;

        do {
            $searchResult = $this->customerRepo->getList(
                $this->createSearchCriteria($page, $pageSize)
            );

            foreach ($searchResult->getItems() as $customer) {
                try {
                    $result = $this->crmAdapter->syncCustomer($customer);
                    $report->addSuccess($customer->getId());
                    $this->syncLog->logSync(
                        $customer->getId(),
                        'customer',
                        $result
                    );
                } catch (\Exception $e) {
                    $report->addFailure($customer->getId(), $e->getMessage());
                }
            }

            $page++;
        } while ($searchResult->getTotalCount() > $page * $pageSize);

        return $report;
    }
}

Webhook for Real-time Sync

namespace Vendor\Integration\Observer;

class CustomerSaveAfter implements ObserverInterface
{
    private CrmAdapterInterface $crmAdapter;
    private ConfigInterface $config;

    public function execute(Observer $observer): void
    {
        if (!$this->config->isRealtimeSyncEnabled()) {
            return;
        }

        $customer = $observer->getEvent()->getCustomer();
        $this->crmAdapter->syncCustomer($customer);
    }
}

Lead and Opportunity Management

Abandoned Cart to Lead

namespace Vendor\Integration\Model\Crm\Cart;

class AbandonedCartToLead
{
    private CrmAdapterInterface $crmAdapter;

    public function process(CartInterface $cart): CrmResult
    {
        $leadData = [
            'FirstName' => $cart->getCustomerFirstname() ?: 'Guest',
            'LastName' => 'Cart Abandoner',
            'Email' => $cart->getCustomerEmail(),
            'Company' => 'Magento Store',
            'LeadSource' => 'Website',
            'Description' => sprintf(
                'Abandoned cart #%s with %d items, "total": %s',
                $cart->getId(),
                $cart->getItemsCount(),
                $cart->getGrandTotal()
            ),
            'Amount' => $cart->getGrandTotal(),
        ];

        return $this->crmAdapter->syncCartAsLead($cart);
    }
}

Order to Opportunity

namespace Vendor\Integration\Model\Crm\Order;

class OrderToOpportunity
{
    public function convert(OrderInterface $order): OpportunityData
    {
        return new OpportunityData([
            'Name' => sprintf('Order #%s', $order->getIncrementId()),
            'AccountId' => $this->getAccountId($order),
            'Amount' => $order->getGrandTotal(),
            'CloseDate' => $order->getCreatedAt(),
            'StageName' => 'Closed Won',
            'Description' => $this->buildDescription($order),
            'Magento_Order_Id__c' => $order->getIncrementId(),
        ]);
    }
}
```"
    },
    {
      "id": "ch4",
      "title": "Field Mapping and Transformation",
      "content": "## Field Mapping Configuration

```php
namespace Vendor\Integration\Model\Crm\Mapping;

interface FieldMapperInterface
{
    public function mapCustomer(CustomerInterface $customer): array;
    public function mapOrder(OrderInterface $order): array;
    public function reverseMap(array $crmData): array;
}

class ConfigurableFieldMapper implements FieldMapperInterface
{
    private array $fieldMap;

    public function __construct(array $fieldMap = [])
    {
        $this->fieldMap = $fieldMap;
    }

    public function mapCustomer(CustomerInterface $customer): array
    {
        $mapped = [];
        foreach ($this->fieldMap as $magentoField => $crmField) {
            $value = $this->getValue($customer, $magentoField);
            $mapped[$crmField] = $this->transform($value, $crmField);
        }
        return $mapped;
    }
}

di.xml Mapping Configuration

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Vendor\Integration\Model\Crm\Mapping\ConfigurableFieldMapper">
        <arguments>
            <argument name="fieldMap" xsi:type="array">
                <item name="firstname" xsi:type="string">FirstName</item>
                <item name="lastname" xsi:type="string">LastName</item>
                <item name="email" xsi:type="string">Email</item>
                <item name="phone" xsi:type="string">Phone</item>
                <item name="taxvat" xsi:type="string">TaxId__c</item>
            </argument>
        </arguments>
    </type>
</config>

Quiz

1. What does a CRM adapter do?

Question 1 options

2. How can abandoned carts become CRM leads?

Question 2 options

3. What is field mapping in CRM integration?

Question 3 options

Flashcards

Question

What does a CRM adapter provide?

Answer

Translation layer between Magento and CRM systems

Question

How to sync in real-time?

Answer

Use observers on save events to trigger CRM sync

Question

What is field mapping?

Answer

Configurable translation between Magento and CRM fields

Question

What data can sync to CRM?

Answer

Customers, orders, carts, leads, opportunities

Revision Notes

Key Takeaways

  • 1. CRM adapters provide standardized integration interfaces
  • 2. Real-time sync uses observers on entity save events
  • 3. Batch sync is more efficient for bulk operations
  • 4. Field mapping translates attribute names between systems
  • 5. Abandoned carts can automatically become CRM leads

Interview Tips

  • Explain real-time vs batch sync trade-offs
  • Discuss how field mapping handles different data formats
  • Describe the lifecycle of a customer sync
  • Talk about error handling in CRM integrations

Cheat Sheet

CRM Integration:
  Adapter → Salesforce/HubSpot/Custom
  Real-time → Observer + async queue
  Batch → Cron job + pagination

Field Mapping:
  firstname → FirstName
  lastname → LastName
  email → Email
  phone → Phone

Sync Types:
  Customer → Contact
  Order → Opportunity
  Cart → Lead