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手动安装
- 创建目录
app/code/EmailVerify/EmailVerification - 复制模块文件
- 运行设置命令:
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'],
]);