• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grueneschweiz / mailchimpservice / 17108989360

20 Aug 2025 08:00PM UTC coverage: 68.598% (-1.6%) from 70.247%
17108989360

push

github

Michael-Schaer
[FEAT] Sync from mailchimp to crm in specific cases

197 of 333 new or added lines in 10 files covered. (59.16%)

4 existing lines in 2 files now uncovered.

959 of 1398 relevant lines covered (68.6%)

11.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.49
/app/Synchronizer/MailchimpToCrmWebhookSynchronizer.php
1
<?php
2

3
namespace App\Synchronizer;
4

5
use App\Exceptions\ConfigException;
6
use App\Exceptions\ParseCrmDataException;
7
use App\Exceptions\ParseMailchimpDataException;
8
use GuzzleHttp\Exception\ClientException;
9
use GuzzleHttp\Exception\GuzzleException;
10
use App\Mail\WrongSubscription;
11
use App\Synchronizer\Mapper\FieldMaps\FieldMapGroup;
12
use Illuminate\Support\Facades\Mail;
13
use App\Http\MailChimpClient;
14
use App\Synchronizer\Mapper\Mapper;
15

16
/**
17
 * @see https://mailchimp.com/developer/marketing/api/list-webhooks/list-webhooks#button--61--0
18
 */
19
class MailchimpToCrmWebhookSynchronizer extends MailchimpToCrmSynchronizer
20
{
21
    private const MC_EMAIL_UPDATE = 'upemail';
22
    private const MC_CLEANED_EMAIL = 'cleaned';
23
    private const MC_SUBSCRIBE = 'subscribe';
24
    private const MC_UNSUBSCRIBE = 'unsubscribe';
25
    private const MC_PROFILE_UPDATE = 'profile';
26

27
    /**
28
     * Handle a webhook event from Mailchimp
29
     *
30
     * @param array $mcData
31
     *
32
     * @throws ConfigException
33
     * @throws ParseMailchimpDataException
34
     * @throws GuzzleException
35
     * @throws ParseCrmDataException
36
     * @throws \Exception
37
     */
38
    public function handleMailchimpUpdate(array $mcData)
39
    {
40
        $mapper = new Mapper($this->config->getFieldMaps());
6✔
41

42
        $email = isset($mcData['data']['new_email']) ? $mcData['data']['new_email'] : $mcData['data']['email'];
6✔
43

44
        $callType = $mcData['type'];
6✔
45
        $mailchimpId = MailChimpClient::calculateSubscriberId($email);
6✔
46

47
        $this->logWebhook('debug', $callType, $mailchimpId, "Sync single record from Mailchimp to CRM.");
6✔
48

49
        switch ($callType) {
50
            case self::MC_SUBSCRIBE:
51
                if (!$this->config->getIgnoreSubscribeThroughMailchimp()) {
2✔
NEW
52
                    $mcData = $this->mcClient->getSubscriber($email);
×
NEW
53
                    $mergeFields = $this->extractMergeFields($mcData);
×
NEW
54
                    if (empty($mergeFields[$this->config->getMailchimpKeyOfCrmId()])) {
×
NEW
55
                        $this->sendMailSubscribeOnlyInWebling($this->config->getDataOwner(), $mcData);
×
NEW
56
                        $this->logWebhook('debug', $callType, $mailchimpId, "Notified data owner.");
×
57
                    }
58
                }
59
                return;
2✔
60

61
            case self::MC_UNSUBSCRIBE:
62
                $mergeFields = $this->extractMergeFields($mcData['data']);
1✔
63
                $crmId = $mergeFields[$this->config->getMailchimpKeyOfCrmId()];
1✔
64
                if (empty($crmId)) {
1✔
NEW
65
                    $this->logWebhook('debug', $callType, $mailchimpId, "Record not linked to crm. No action taken.");
×
NEW
66
                    return;
×
67
                }
68

69
                // set all subscriptions that are configured in the currently loaded config file, to NO
70
                try {
71
                    $get = $this->crmClient->get('member/' . $crmId);
1✔
NEW
72
                } catch (ClientException $e) {
×
NEW
73
                    if ($e->getResponse()->getStatusCode() === 404) {
×
NEW
74
                        $this->logWebhook('debug', $callType, $mailchimpId, "Tried to unsubscribe member, but member not found in Webling. So there is also nothing to unsubscribe. No action taken.", $crmId);
×
NEW
75
                        return;
×
76
                    }
77

NEW
78
                    throw $e;
×
79
                }
80
                $crmData = json_decode((string)$get->getBody(), true);
1✔
81
                $crmData = $this->unsubscribeAll($crmData);
1✔
82
                $this->logWebhook('debug', $callType, $mailchimpId, "Unsubscribe member in crm.", $crmId);
1✔
83
                break;
1✔
84

85
            case self::MC_CLEANED_EMAIL:
86
                // set email1 to invalid
87
                // add note 'email set to invalid because it bounced in mailchimp'
88
                if ('hard' !== $mcData['data']['reason']) {
1✔
NEW
89
                    $this->logWebhook('debug', $callType, $mailchimpId, "Bounce not hard. No action taken.");
×
NEW
90
                    return;
×
91
                }
92
                $mcData = $this->mcClient->getSubscriber($email);
1✔
93
                $mergeFields = $this->extractMergeFields($mcData);
1✔
94
                $crmId = $mergeFields[$this->config->getMailchimpKeyOfCrmId()];
1✔
95
                $note = sprintf("%s: Mailchimp reported the email as invalid. Email status changed.", date('Y-m-d H:i'));
1✔
96
                $crmData['emailStatus'] = [['value' => 'invalid', 'mode' => CrmValue::MODE_REPLACE]];
1✔
97
                $crmData['notesCountry'] = [['value' => $note, 'mode' => CrmValue::MODE_APPEND]];
1✔
98
                $this->logWebhook('debug', $callType, $mailchimpId, "Mark email invalid in crm.", $crmId);
1✔
99
                break;
1✔
100

101
            case self::MC_PROFILE_UPDATE:
102
                // get subscriber from mailchimp (so we have the interessts (groups) in a usable format)
103
                // update email1, subscriptions
104
                $mcData = $this->mcClient->getSubscriber($email);
1✔
105
                $mergeFields = $this->extractMergeFields($mcData);
1✔
106
                $crmId = $mergeFields[$this->config->getMailchimpKeyOfCrmId()];
1✔
107
                $crmData = $mapper->mailchimpToCrm($mcData);
1✔
108
                $this->logWebhook('debug', $callType, $mailchimpId, "Update email, subscriptions in crm.", $crmId);
1✔
109
                break;
1✔
110

111
            case self::MC_EMAIL_UPDATE:
112
                // update email1
113
                $mcData = $this->mcClient->getSubscriber($email);
1✔
114
                $mergeFields = $this->extractMergeFields($mcData);
1✔
115
                $crmId = $mergeFields[$this->config->getMailchimpKeyOfCrmId()];
1✔
116
                $crmValue = $this->updateEmail($mcData)[0];
1✔
117
                $crmData = [$crmValue->getKey() => [['value' => $crmValue->getValue(), 'mode' => $crmValue->getMode()]]];
1✔
118
                $this->logWebhook('debug', $callType, $mailchimpId, "Update email in crm.", $crmId);
1✔
119
                break;
1✔
120

121
            default:
NEW
122
                $this->logWebhook('error', $callType, $mailchimpId, __METHOD__ . " was called with an undefined webhook event.");
×
NEW
123
                return;
×
124
        }
125

126
        try {
127
            $this->crmClient->put('member/' . $crmId, $crmData);
4✔
NEW
128
        } catch (ClientException $e) {
×
NEW
129
            if (404 === $e->getResponse()->getStatusCode()) {
×
NEW
130
                $this->logWebhook('info', $callType, $mailchimpId, "Member not found in Webling. Action could not be executed: $callType", $crmId);
×
NEW
131
                return;
×
132
            }
NEW
133
            throw $e;
×
134
        }
135

136
        $this->logWebhook('debug', $callType, $mailchimpId, "Sync successful");
4✔
137
    }
138

139
    /**
140
     * Inform data owner that he should only add contact in the crm not in mailchimp
141
     *
142
     * @param array $dataOwner
143
     * @param array $mcData
144
     */
145
    private function sendMailSubscribeOnlyInWebling(array $dataOwner, array $mcData)
146
    {
NEW
147
        $mergeFields = $this->extractMergeFields($mcData);
×
148

NEW
149
        $mailData = new \stdClass();
×
NEW
150
        $mailData->dataOwnerName = $dataOwner['name'];
×
NEW
151
        $mailData->contactFirstName = $mergeFields['FNAME']; // todo: check if we cant get the field keys dynamically
×
NEW
152
        $mailData->contactLastName = $mergeFields['LNAME']; // todo: dito
×
NEW
153
        $mailData->contactEmail = $mcData['email_address'];
×
NEW
154
        $mailData->adminEmail = env('ADMIN_EMAIL');
×
NEW
155
        $mailData->configName = $this->configName;
×
156

NEW
157
        Mail::to($dataOwner['email'])
×
NEW
158
            ->send(new WrongSubscription($mailData));
×
159
    }
160

161
    /**
162
     * Return $crmData with all subscriptions disabled
163
     *
164
     * @param array $crmData
165
     *
166
     * @return array in crmData format
167
     *
168
     * @throws \App\Exceptions\ConfigException
169
     * @throws \App\Exceptions\ParseCrmDataException
170
     * @throws \App\Exceptions\ParseMailchimpDataException
171
     */
172
    private function unsubscribeAll(array $crmData): array
173
    {
174
        $mapper = new Mapper($this->config->getFieldMaps());
1✔
175
        $mcData = $mapper->crmToMailchimp($crmData);
1✔
176

177
        foreach ($mcData[FieldMapGroup::MAILCHIMP_PARENT_KEY] as $key => $value) {
1✔
178
            $mcData[FieldMapGroup::MAILCHIMP_PARENT_KEY][$key] = false;
1✔
179
        }
180

181
        return $mapper->mailchimpToCrm($mcData);
1✔
182
    }
183

184
    /**
185
     * Update the email field in $crmData according to the email in $mcData
186
     *
187
     * @param array $mcData
188
     *
189
     * @return CrmValue[] of the email field
190
     *
191
     * @throws \App\Exceptions\ConfigException
192
     * @throws \App\Exceptions\ParseMailchimpDataException
193
     */
194
    private function updateEmail(array $mcData): array
195
    {
196
        foreach ($this->config->getFieldMaps() as $map) {
1✔
197
            if ($map->isEmail()) {
1✔
198
                $map->addMailchimpData($mcData);
1✔
199

200
                return $map->getCrmData();
1✔
201
            }
202
        }
203

NEW
204
        throw new ConfigException('No field of type "email"');
×
205
    }
206

207
    /**
208
     * Either merges or merge_fields
209
     */
210
    private function extractMergeFields(array $mcData)
211
    {
212
        return $mcData['merges'] ?? $mcData['merge_fields'];
4✔
213
    }
214
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc