EmailVerify LogoEmailVerify

Magento

Email checker for Magento. Verify customer emails in Magento 2 checkout flow.

使用 EmailVerify 与 Magento 2 集成,在结账、注册和整个电子商务平台中验证客户电子邮件。

集成方式

方式最佳用途复杂度
模块完整集成中等
API 直接调用自定义实现
观察者事件特定触发器中等

模块安装

通过 Composer

composer require emailverify/magento2-module
bin/magento module:enable EmailVerify_EmailVerification
bin/magento setup:upgrade
bin/magento cache:clean

手动安装

  1. 创建目录 app/code/EmailVerify/EmailVerification
  2. 复制模块文件
  3. 运行设置命令:
bin/magento module:enable EmailVerify_EmailVerification
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:clean

模块结构

app/code/EmailVerify/EmailVerification/
├── etc/
│   ├── module.xml
│   ├── di.xml
│   ├── adminhtml/
│   │   └── system.xml
│   └── frontend/
│       ├── events.xml
│       └── routes.xml
├── Model/
│   └── EmailVerifier.php
├── Observer/
│   └── ValidateCustomerEmail.php
├── Controller/
│   └── Ajax/
│       └── Verify.php
├── Plugin/
│   └── CheckoutEmailValidation.php
├── view/
│   └── frontend/
│       ├── layout/
│       │   └── checkout_index_index.xml
│       ├── web/
│       │   ├── js/
│       │   │   └── email-verification.js
│       │   └── css/
│       │       └── email-verification.css
│       └── requirejs-config.js
└── registration.php

核心模块文件

registration.php

<?php
use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(
    ComponentRegistrar::MODULE,
    'EmailVerify_EmailVerification',
    __DIR__
);

etc/module.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="EmailVerify_EmailVerification" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Customer"/>
            <module name="Magento_Checkout"/>
        </sequence>
    </module>
</config>

etc/adminhtml/system.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <section id="emailverify" translate="label" sortOrder="500" showInDefault="1" showInWebsite="1" showInStore="1">
            <label>EmailVerify</label>
            <tab>general</tab>
            <resource>EmailVerify_EmailVerification::config</resource>
            <group id="general" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>常规设置</label>
                <field id="enabled" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>启用电子邮件验证</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
                <field id="api_key" translate="label" type="obscure" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>API 密钥</label>
                    <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model>
                </field>
                <field id="block_disposable" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>阻止一次性电子邮件</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
                <field id="block_invalid" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>阻止无效电子邮件</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
            </group>
        </section>
    </system>
</config>

电子邮件验证器模型

<?php
// Model/EmailVerifier.php

namespace EmailVerify\EmailVerification\Model;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\HTTP\Client\Curl;
use Magento\Framework\Serialize\Serializer\Json;
use Magento\Store\Model\ScopeInterface;
use Psr\Log\LoggerInterface;

class EmailVerifier
{
    private const API_URL = 'https://api.emailverify.ai/v1/verify';

    private ScopeConfigInterface $scopeConfig;
    private Curl $curl;
    private Json $json;
    private LoggerInterface $logger;

    public function __construct(
        ScopeConfigInterface $scopeConfig,
        Curl $curl,
        Json $json,
        LoggerInterface $logger
    ) {
        $this->scopeConfig = $scopeConfig;
        $this->curl = $curl;
        $this->json = $json;
        $this->logger = $logger;
    }

    public function isEnabled(): bool
    {
        return $this->scopeConfig->isSetFlag(
            'emailverify/general/enabled',
            ScopeInterface::SCOPE_STORE
        );
    }

    public function verify(string $email): array
    {
        if (!$this->isEnabled()) {
            return ['success' => true, 'status' => 'skipped'];
        }

        $apiKey = $this->scopeConfig->getValue(
            'emailverify/general/api_key',
            ScopeInterface::SCOPE_STORE
        );

        if (empty($apiKey)) {
            $this->logger->warning('EmailVerify: API key not configured');
            return ['success' => false, 'error' => 'API key not configured'];
        }

        try {
            $this->curl->setHeaders([
                'Authorization' => 'Bearer ' . $apiKey,
                'Content-Type' => 'application/json',
            ]);

            $this->curl->setTimeout(10);
            $this->curl->post(self::API_URL, $this->json->serialize(['email' => $email]));

            $response = $this->json->unserialize($this->curl->getBody());

            return [
                'success' => true,
                'status' => $response['status'] ?? 'unknown',
                'score' => $response['score'] ?? null,
                'disposable' => $response['result']['disposable'] ?? false,
                'role' => $response['result']['role'] ?? false,
            ];

        } catch (\Exception $e) {
            $this->logger->error('EmailVerify API error: ' . $e->getMessage());
            return ['success' => false, 'error' => $e->getMessage()];
        }
    }

    public function shouldBlockEmail(array $result): bool
    {
        if (!$result['success']) {
            return false; // Don't block on API errors
        }

        $blockInvalid = $this->scopeConfig->isSetFlag(
            'emailverify/general/block_invalid',
            ScopeInterface::SCOPE_STORE
        );

        $blockDisposable = $this->scopeConfig->isSetFlag(
            'emailverify/general/block_disposable',
            ScopeInterface::SCOPE_STORE
        );

        if ($blockInvalid && $result['status'] === 'invalid') {
            return true;
        }

        if ($blockDisposable && ($result['disposable'] ?? false)) {
            return true;
        }

        return false;
    }

    public function getBlockReason(array $result): string
    {
        if ($result['status'] === 'invalid') {
            return __('请输入有效的电子邮件地址。');
        }

        if ($result['disposable'] ?? false) {
            return __('不接受临时电子邮件地址。请使用永久电子邮件。');
        }

        return __('电子邮件验证失败。请尝试不同的电子邮件地址。');
    }
}

结账集成

结账电子邮件验证的插件

<?php
// Plugin/CheckoutEmailValidation.php

namespace EmailVerify\EmailVerification\Plugin;

use EmailVerify\EmailVerification\Model\EmailVerifier;
use Magento\Checkout\Api\Data\ShippingInformationInterface;
use Magento\Checkout\Api\ShippingInformationManagementInterface;
use Magento\Framework\Exception\InputException;

class CheckoutEmailValidation
{
    private EmailVerifier $emailVerifier;

    public function __construct(EmailVerifier $emailVerifier)
    {
        $this->emailVerifier = $emailVerifier;
    }

    public function beforeSaveAddressInformation(
        ShippingInformationManagementInterface $subject,
        $cartId,
        ShippingInformationInterface $addressInformation
    ): array {
        $shippingAddress = $addressInformation->getShippingAddress();
        $email = $shippingAddress->getEmail();

        if ($email) {
            $result = $this->emailVerifier->verify($email);

            if ($this->emailVerifier->shouldBlockEmail($result)) {
                throw new InputException(
                    __($this->emailVerifier->getBlockReason($result))
                );
            }
        }

        return [$cartId, $addressInformation];
    }
}

di.xml 配置

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Checkout\Api\ShippingInformationManagementInterface">
        <plugin name="emailverify_checkout_email_validation"
                type="EmailVerify\EmailVerification\Plugin\CheckoutEmailValidation"
                sortOrder="10"/>
    </type>
</config>

客户注册验证

观察者

<?php
// Observer/ValidateCustomerEmail.php

namespace EmailVerify\EmailVerification\Observer;

use EmailVerify\EmailVerification\Model\EmailVerifier;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\Exception\LocalizedException;

class ValidateCustomerEmail implements ObserverInterface
{
    private EmailVerifier $emailVerifier;

    public function __construct(EmailVerifier $emailVerifier)
    {
        $this->emailVerifier = $emailVerifier;
    }

    public function execute(Observer $observer): void
    {
        $customer = $observer->getEvent()->getCustomer();
        $email = $customer->getEmail();

        if (empty($email)) {
            return;
        }

        $result = $this->emailVerifier->verify($email);

        if ($this->emailVerifier->shouldBlockEmail($result)) {
            throw new LocalizedException(
                __($this->emailVerifier->getBlockReason($result))
            );
        }

        // 将验证结果存储为客户属性
        $customer->setCustomAttribute('email_verification_status', $result['status']);
        $customer->setCustomAttribute('email_verification_score', $result['score'] ?? '');
    }
}

etc/frontend/events.xml 配置

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="customer_save_before">
        <observer name="emailverify_validate_customer_email"
                  instance="EmailVerify\EmailVerification\Observer\ValidateCustomerEmail"/>
    </event>
</config>

AJAX 验证控制器

<?php
// Controller/Ajax/Verify.php

namespace EmailVerify\EmailVerification\Controller\Ajax;

use EmailVerify\EmailVerification\Model\EmailVerifier;
use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Controller\Result\JsonFactory;

class Verify implements HttpPostActionInterface
{
    private RequestInterface $request;
    private JsonFactory $jsonFactory;
    private EmailVerifier $emailVerifier;

    public function __construct(
        RequestInterface $request,
        JsonFactory $jsonFactory,
        EmailVerifier $emailVerifier
    ) {
        $this->request = $request;
        $this->jsonFactory = $jsonFactory;
        $this->emailVerifier = $emailVerifier;
    }

    public function execute()
    {
        $result = $this->jsonFactory->create();

        $email = $this->request->getParam('email');

        if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return $result->setData([
                'success' => false,
                'message' => __('电子邮件格式无效'),
            ]);
        }

        $verification = $this->emailVerifier->verify($email);

        if (!$verification['success']) {
            return $result->setData([
                'success' => true,
                'valid' => true, // 在 API 出错时允许
            ]);
        }

        $shouldBlock = $this->emailVerifier->shouldBlockEmail($verification);

        return $result->setData([
            'success' => true,
            'valid' => !$shouldBlock,
            'status' => $verification['status'],
            'message' => $shouldBlock ? $this->emailVerifier->getBlockReason($verification) : '',
        ]);
    }
}

etc/frontend/routes.xml 配置

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="standard">
        <route id="emailverify" frontName="emailverify">
            <module name="EmailVerify_EmailVerification"/>
        </route>
    </router>
</config>

前端 JavaScript

requirejs-config.js

var config = {
    map: {
        '*': {
            'emailverifyEmailVerification': 'EmailVerify_EmailVerification/js/email-verification'
        }
    }
};

view/frontend/web/js/email-verification.js 脚本

define([
    'jquery',
    'mage/url',
    'mage/translate'
], function($, urlBuilder, $t) {
    'use strict';

    return function(config, element) {
        var $input = $(element);
        var $statusEl = $('<span class="bv-email-status"></span>');
        var verifyTimeout;
        var lastVerifiedEmail = '';

        $input.after($statusEl);

        $input.on('blur change', function() {
            var email = $input.val();

            if (!email || email === lastVerifiedEmail) {
                return;
            }

            if (!isValidEmailFormat(email)) {
                $statusEl.html('').removeClass('valid invalid verifying');
                return;
            }

            clearTimeout(verifyTimeout);

            verifyTimeout = setTimeout(function() {
                verifyEmail(email);
            }, 500);
        });

        function verifyEmail(email) {
            $statusEl
                .html('<span class="loader"></span> ' + $t('验证中...'))
                .addClass('verifying')
                .removeClass('valid invalid');

            $.ajax({
                url: urlBuilder.build('emailverify/ajax/verify'),
                type: 'POST',
                data: { email: email },
                dataType: 'json',
                success: function(response) {
                    $statusEl.removeClass('verifying');
                    lastVerifiedEmail = email;

                    if (response.success) {
                        if (response.valid) {
                            $statusEl
                                .html('<span class="icon-check"></span> ' + $t('电子邮件已验证'))
                                .addClass('valid')
                                .removeClass('invalid');
                        } else {
                            $statusEl
                                .html('<span class="icon-x"></span> ' + response.message)
                                .addClass('invalid')
                                .removeClass('valid');
                        }
                    }
                },
                error: function() {
                    $statusEl.html('').removeClass('verifying valid invalid');
                }
            });
        }

        function isValidEmailFormat(email) {
            return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
        }
    };
});

view/frontend/web/css/email-verification.css 样式

.bv-email-status {
    display: block;
    margin-top: 5px;
    font-size: 12px;
}

.bv-email-status.verifying {
    color: #666;
}

.bv-email-status.valid {
    color: #006400;
}

.bv-email-status.invalid {
    color: #dc3545;
}

.bv-email-status .loader {
    display: inline-block;
    width: 12px;
    height: 12px;
    border: 2px solid #ccc;
    border-top-color: #333;
    border-radius: 50%;
    animation: bv-spin 1s linear infinite;
}

@keyframes bv-spin {
    to { transform: rotate(360deg); }
}

.bv-email-status .icon-check:before {
    content: '✓';
}

.bv-email-status .icon-x:before {
    content: '✗';
}

结账布局

view/frontend/layout/checkout_index_index.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <head>
        <css src="EmailVerify_EmailVerification::css/email-verification.css"/>
    </head>
    <body>
        <referenceBlock name="checkout.root">
            <arguments>
                <argument name="jsLayout" xsi:type="array">
                    <item name="components" xsi:type="array">
                        <item name="checkout" xsi:type="array">
                            <item name="children" xsi:type="array">
                                <item name="steps" xsi:type="array">
                                    <item name="children" xsi:type="array">
                                        <item name="shipping-step" xsi:type="array">
                                            <item name="children" xsi:type="array">
                                                <item name="shippingAddress" xsi:type="array">
                                                    <item name="children" xsi:type="array">
                                                        <item name="shipping-address-fieldset" xsi:type="array">
                                                            <item name="children" xsi:type="array">
                                                                <item name="email" xsi:type="array">
                                                                    <item name="component" xsi:type="string">EmailVerify_EmailVerification/js/view/form/element/email</item>
                                                                </item>
                                                            </item>
                                                        </item>
                                                    </item>
                                                </item>
                                            </item>
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </item>
                </argument>
            </arguments>
        </referenceBlock>
    </body>
</page>

GraphQL 支持

用于无头 Magento 实现。

etc/schema.graphqls

type Mutation {
    verifyEmail(email: String!): EmailVerificationResult @resolver(class: "EmailVerify\\EmailVerification\\Model\\Resolver\\VerifyEmail")
}

type EmailVerificationResult {
    success: Boolean!
    valid: Boolean
    status: String
    message: String
}

Model/Resolver/VerifyEmail.php 解析器

<?php
namespace EmailVerify\EmailVerification\Model\Resolver;

use EmailVerify\EmailVerification\Model\EmailVerifier;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;

class VerifyEmail implements ResolverInterface
{
    private EmailVerifier $emailVerifier;

    public function __construct(EmailVerifier $emailVerifier)
    {
        $this->emailVerifier = $emailVerifier;
    }

    public function resolve(
        Field $field,
        $context,
        ResolveInfo $info,
        array $value = null,
        array $args = null
    ): array {
        $email = $args['email'] ?? '';

        if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return [
                'success' => false,
                'valid' => false,
                'message' => '电子邮件格式无效',
            ];
        }

        $result = $this->emailVerifier->verify($email);
        $shouldBlock = $this->emailVerifier->shouldBlockEmail($result);

        return [
            'success' => true,
            'valid' => !$shouldBlock,
            'status' => $result['status'] ?? 'unknown',
            'message' => $shouldBlock ? $this->emailVerifier->getBlockReason($result) : '',
        ];
    }
}

GraphQL 使用方法

mutation {
  verifyEmail(email: "customer@example.com") {
    success
    valid
    status
    message
  }
}

最佳实践

1. 缓存验证结果

use Magento\Framework\App\CacheInterface;

class EmailVerifier
{
    private CacheInterface $cache;

    public function verify(string $email): array
    {
        $cacheKey = 'bv_email_' . md5($email);
        $cached = $this->cache->load($cacheKey);

        if ($cached) {
            return $this->json->unserialize($cached);
        }

        $result = $this->doVerify($email);

        $this->cache->save(
            $this->json->serialize($result),
            $cacheKey,
            [],
            86400 // 24 小时
        );

        return $result;
    }
}

2. 优雅地处理 API 故障

// 永不在 API 错误时阻止结账
if (!$result['success']) {
    $this->logger->warning('EmailVerify API 错误,允许结账');
    return; // 允许结账继续
}

3. 记录用于分析

$this->logger->info('电子邮件验证', [
    'email' => $this->maskEmail($email),
    'status' => $result['status'],
    'score' => $result['score'],
    'disposable' => $result['disposable'],
]);

相关资源

On this page