Context
ログイン・会員グループ
ゲスト、一般会員、B2B会員を判定し、現在のグループ名や日本語ラベルを取得できます。
会員グループ別価格&数量別割引マネージャー for EC-CUBE 4.2 / 4.3
Twig APIを利用して、ログイン状態・会員グループ・商品タグ・カテゴリ・B2B価格ルールに応じた表示を安全にカスタマイズする方法を説明します。
EC-CUBE 4.2 / 4.3対応
Context
ゲスト、一般会員、B2B会員を判定し、現在のグループ名や日本語ラベルを取得できます。
Product
商品ID、タグ、カテゴリを個別または複数条件で判定し、カスタマイズ対象を絞り込めます。
Price
現在の会員に適用される表示単価、割引額、価格ルール、数量別割引段階を参照できます。
グループ名・タグ名・カテゴリ名は管理画面で変更できます。本番の判定条件にはIDを使用し、名前によるAPIは簡易カスタマイズや表示補助に使用してください。
{% set employee_group_id = 3 %}
{% if b2b_in_group_id(employee_group_id) %}
社員向けコンテンツ
{% endif %}
app/template/<テンプレート名>/Product/list.twigapp/template/<テンプレート名>/Product/detail.twigapp/template/default/通常はID・名前・真偽値を返すスカラーAPIを使用してください。b2b_customer()、b2b_group()、b2b_applicable_rule()、b2b_applicable_tier()はEntityを返し、b2b_price_context()にもrule・tierのEntityが含まれます。これらは信頼できるテンプレートで必要なプロパティだけを参照するための上級者向けAPIです。Entity全体をdumpしたり、会員の個人情報や内部設定を画面・ログへ出力したりしないでください。
Twig API
引数を省略すると、現在ログイン中のフロント会員を参照します。管理画面ユーザーはフロント会員として扱いません。
| Twig関数 | 戻り値 | 用途 |
|---|---|---|
b2b_is_logged_in() | bool | フロント会員がログイン中か |
b2b_is_guest() | bool | ゲストまたはフロント会員以外か |
b2b_customer() | Customer|null | 現在のフロント会員。ゲスト時はnull |
b2b_customer_id() | int|null | 現在の会員ID |
b2b_group() | B2BGroup|null | 現在のB2Bグループ |
b2b_group_id() | int|null | 現在のB2BグループID |
b2b_group_name() | string|null | 現在のB2Bグループ名 |
b2b_group_label() | string | グループ名、一般会員、またはゲスト |
b2b_has_group() | bool | B2Bグループに割り当て済みか |
b2b_in_group_id(groupId) | bool | 指定グループIDと一致するか |
b2b_in_group_name(groupName) | bool | 指定グループ名と一致するか |
b2b_in_any_group_ids(groupIds) | bool | 指定した複数のグループIDのいずれかと一致するか |
b2b_in_any_group_names(groupNames) | bool | 指定した複数のグループ名のいずれかと一致するか |
<p>会員区分:{{ b2b_group_label() }}</p>
{% if b2b_in_any_group_ids([3, 5, 8]) %}
許可された会員向けコンテンツ
{% endif %}
Twig API
Productには商品エンティティを渡します。複数条件は配列で指定できます。
| Twig関数 | 戻り値 | 用途 |
|---|---|---|
b2b_product_is_id(Product, productId) | bool | 商品IDが一致するか |
b2b_product_has_tag_id(Product, tagId) | bool | タグIDが設定されているか |
b2b_product_has_tag_name(Product, tagName) | bool | タグ名が設定されているか |
b2b_product_has_any_tag_ids(Product, tagIds) | bool | 指定タグIDのいずれかを持つか |
b2b_product_has_any_tag_names(Product, tagNames) | bool | 指定タグ名のいずれかを持つか |
b2b_product_has_category_id(Product, categoryId) | bool | カテゴリIDが直接設定されているか |
b2b_product_has_category_name(Product, categoryName) | bool | カテゴリ名が直接設定されているか |
b2b_product_has_any_category_ids(Product, categoryIds) | bool | 指定カテゴリIDのいずれかに直接所属するか |
b2b_product_has_any_category_names(Product, categoryNames) | bool | 指定カテゴリ名のいずれかに直接所属するか |
b2b_product_matches(Product, criteria = {}) | bool | 商品・カテゴリ・タグの条件をまとめて判定 |
b2b_can_view_product(Product, allowedGroupIds, criteria = {}) | bool | 商品を表示してよいかを画面用に判定 |
b2b_can_purchase_product(Product, allowedGroupIds, criteria = {}) | bool | 購入UIを表示してよいかを画面用に判定 |
判定仕様:criteriaが空の場合は、渡した商品そのものを制限対象として扱います。条件が指定され、商品がその条件に一致しない場合は制限対象外のためtrueです。制限対象商品では、現在のグループIDがallowedGroupIdsに含まれる場合だけtrueです。
カテゴリ判定は商品に直接設定されたカテゴリとの一致です。親カテゴリ・子カテゴリの自動展開は行いません。
criteriaで使用できるキーはproduct_ids、tag_ids、tag_names、category_ids、category_namesです。各条件はOR、名前は完全一致で評価されます。ID配列には整数、名前配列には空でない文字列を指定してください。トップレベルの空条件{}はすべての商品を対象にします。一方、指定したキーの配列が空(例:{tag_ids: []})またはキー・値が不正な場合は、設定ミスによる迂回を防ぐためアクセス判定がfalseになります。
次の例は、商品ID 100、カテゴリID 12、タグID 7または9のいずれかに一致する商品を対象にします。
{% set target = {
product_ids: [100],
category_ids: [12],
tag_ids: [7, 9]
} %}
{% if b2b_product_matches(Product, target) %}
カスタマイズ対象商品
{% endif %}
Twig API
価格計算には商品規格のProductClassと数量を渡します。ルール判定にはProductを渡します。
| Twig関数 | 戻り値 | 用途 |
|---|---|---|
b2b_price_context(ProductClass, quantity = 1) | { base_unit_price, unit_price, quantity, discount, rule, tier, matched } | 価格・ルール・割引段階を1回の整合した計算で取得(推奨) |
b2b_price_result(ProductClass, quantity = 1) | { discount, unit_price } | 数量適用後の割引総額と表示単価 |
b2b_display_unit_price(ProductClass, quantity = 1) | int | 数量適用後の単価 |
b2b_discount_amount(ProductClass, quantity = 1) | int | 対象明細の割引総額 |
b2b_applicable_rule(Product) | B2BPriceRule|null | 現在の会員に適用可能な価格ルール |
b2b_has_applicable_rule(Product) | bool | 適用可能な価格ルールがあるか |
b2b_applicable_tier(Product, quantity = 1) | B2BPriceTier|null | 指定数量に適用される割引段階 |
b2b_has_tier_rule(Product) | bool | 数量別割引ルールを持つ対象商品か |
同じ計算で決定された価格・ルール・割引段階を表示する場合は、個別の関数を組み合わせず、この統合ヘルパーを使用します。
{% set context = b2b_price_context(ProductClass, quantity) %}
<span>単価:{{ context.unit_price|price }}</span>
{% if context.discount > 0 %}
<span>割引額:-{{ context.discount|price }}</span>
{% endif %}
{% if context.matched %}
<span>適用ルール:{{ context.rule.name }}</span>
{% endif %}
{% set rule = b2b_applicable_rule(Product) %}
{% set tier = b2b_applicable_tier(Product, quantity) %}
{% if rule %}
<p>適用ルール:{{ rule.name }}</p>
{% endif %}
{% if tier %}
<p>適用開始数量:{{ tier.minQty }}</p>
{% endif %}
b2b_applicable_rule()は、現在のグループに属する有効なルールのうち、商品に一致し、割引段階を1件以上持つルールを返します。この時点では指定数量が最小数量へ到達していることまでは意味しません。
b2b_applicable_tier()は指定数量を満たす割引段階を探します。先に見つかったルールの最小数量を満たさない場合は、後続の一致ルールも確認します。ゲスト・一般会員・該当なしの場合はnullです。
価格・ルール・割引段階を同時表示する場合はb2b_price_context()を使用してください。各値が同じルールと割引段階から計算され、個別関数の重複クエリや組み合わせの不一致を避けられます。個別関数は単一情報の参照に使用できます。商品一覧では結果をTwig変数へ保存し、ループ内の重複呼び出しを避けてください。標準の商品一覧価格はプラグインが既に差し込むため、独自表示が必要な場合だけ使用してください。
Product/detail.twig・Product/list.twig
以下のIDは例です。管理画面やデータベースで実際のグループ・タグ・カテゴリIDを確認して置き換えてください。
Example 1
標準の価格表示部分とカートフォーム部分を、次の条件で囲みます。B2Bグループ未割当の一般会員もログイン済みなので表示されます。
{% if b2b_is_logged_in() %}
{# 既存の価格表示をここへ配置 #}
<p class="ec-price">{{ Product.getPrice02IncTaxMin|price }}</p>
{# 既存のカートフォームをここへ配置 #}
{{ form_start(form, {attr: {id: 'form1'}}) }}
{# 規格・数量・カートボタン等 #}
{{ form_end(form) }}
{% else %}
<p class="ec-color-red">
価格の確認・ご購入にはログインが必要です。
</p>
<a href="{{ url('mypage_login') }}">ログインする</a>
{% endif %}
Example 2
{% for Product in pagination %}内にある価格・カート部分を囲みます。商品名や画像はゲストにも表示されます。
{% for Product in pagination %}
<div class="ec-shelfGrid__item">
{# 商品画像・商品名は常に表示 #}
<a href="{{ url('product_detail', {id: Product.id}) }}">
{{ Product.name }}
</a>
{% if b2b_is_logged_in() %}
{# 既存の価格表示・カートボタン #}
<p>{{ Product.getPrice02IncTaxMin|price }}</p>
{% else %}
<p>ログイン後に価格をご確認いただけます。</p>
{% endif %}
</div>
{% endfor %}
Example 3
例ではタグID 12を社員限定商品、グループID 3を社員グループとして扱います。対象外の商品は通常どおり購入できます。
{% set employee_group_ids = [3] %}
{% set employee_only = {tag_ids: [12]} %}
{% set can_buy = b2b_can_purchase_product(
Product,
employee_group_ids,
employee_only
) %}
{% if can_buy %}
{# 既存のカートフォーム・購入ボタン #}
<button type="submit">カートに入れる</button>
{% elseif b2b_is_guest() %}
<p>この商品を購入するにはログインしてください。</p>
{% else %}
<p>この商品は社員グループ限定です。</p>
{% endif %}
Example 4
商品を1件ずつTwigへ追加せず、管理画面で対象タグ・カテゴリを設定して運用できます。条件内のIDはいずれかに一致すると対象になります。
{% set restricted_products = {
category_ids: [20, 21],
tag_ids: [12, 13]
} %}
{% set can_buy = b2b_can_purchase_product(
Product,
[3],
restricted_products
) %}
{% if can_buy %}
{# 購入UI #}
{% else %}
<p>ご契約グループでは購入できません。</p>
{% endif %}
Example 5
購入UIだけでなく商品カード全体を隠す場合は、商品カードの外側でb2b_can_view_product()を判定します。
{% set restricted_products = {tag_ids: [12]} %}
{% for Product in pagination %}
{% set can_view = b2b_can_view_product(
Product,
[3],
restricted_products
) %}
{% if can_view %}
<div class="ec-shelfGrid__item">
{# 商品カード全体 #}
</div>
{% endif %}
{% endfor %}
ページネーションの件数はEC-CUBEが取得した全商品を基準にするため、Twigでカードを隠すと表示件数が少なくなる場合があります。検索結果から完全に除外するには、商品検索クエリ側のカスタマイズが別途必要です。
Example 6
<dl>
<dt>会員区分</dt>
<dd>{{ b2b_group_label() }}</dd>
{% if b2b_has_group() %}
<dt>B2BグループID</dt>
<dd>{{ b2b_group_id() }}</dd>
{% endif %}
</dl>
| 状態 | b2b_group_label() | b2b_group_id() |
|---|---|---|
| ゲスト | ゲスト | null |
| グループ未割当会員 | 一般会員 | null |
| B2B会員 | 設定されたグループ名 | グループID |
Example 7
{% set quantity = 5 %}
{% set context = b2b_price_context(ProductClass, quantity) %}
<p>通常単価:{{ context.base_unit_price|price }}</p>
<p>B2B単価:{{ context.unit_price|price }}</p>
{% if context.discount > 0 %}
<p>数量{{ context.quantity }}点の割引額:-{{ context.discount|price }}</p>
{% endif %}
{% if context.rule %}
<p>適用ルール:{{ context.rule.name }}</p>
{% endif %}
{% if context.tier %}
<p>適用開始数量:{{ context.tier.minQty }}点</p>
{% endif %}
サーバー側でも同じ条件を検証
Twig関数の内部ロジックはサービスへ分離されています。独自プラグインやイベントリスナーでは、Twigを呼び出さず対応するサービスをDIして再利用してください。
Service\B2BCustomerContextService\B2BProductMatcherService\B2BAccessServiceService\B2BPriceCalculatorストア画面では現在ログイン中の会員を使うメソッド、受注・バッチ・外部連携では対象会員を明示するメソッドを使用します。
| PHPメソッド | 対象会員 | 用途 |
|---|---|---|
canViewProduct(Product $Product, array $allowedGroupIds, array $criteria = []) | 現在のストア会員 | ストア画面の表示判定 |
canPurchaseProduct(Product $Product, array $allowedGroupIds, array $criteria = []) | 現在のストア会員 | ストア画面の購入UI判定 |
canCustomerViewProduct(?Customer $Customer, Product $Product, array $allowedGroupIds, array $criteria = []) | 引数で指定 | 受注・バッチ等での表示ポリシー判定 |
canCustomerPurchaseProduct(?Customer $Customer, Product $Product, array $allowedGroupIds, array $criteria = []) | 引数で指定 | カート・注文・外部連携での購入ポリシー判定 |
特定のCustomerを明示して調べる場合は、B2BCustomerGroupRepositoryの次のメソッドを利用できます。現在のログイン会員を調べるだけなら、認証状態を安全に扱うB2BCustomerContextを推奨します。
| PHPメソッド | 戻り値 | 用途 |
|---|---|---|
findAssignmentByCustomer(?Customer $Customer) | B2BCustomerGroup|null | 会員とグループの割当レコードを取得 |
findGroupByCustomer(?Customer $Customer) | B2BGroup|null | 会員に割り当てられたグループを取得 |
hasGroupForCustomer(?Customer $Customer) | bool | 会員にB2Bグループが割り当て済みか |
isCustomerInGroupId(?Customer $Customer, int $groupId) | bool | 会員が指定グループIDに属するか |
isCustomerInGroupName(?Customer $Customer, string $groupName) | bool | 会員が指定グループ名に属するか |
拒否時は単にボタンを隠すのではなく、サーバー側で処理を中止し、利用者向けメッセージを返してください。注文確定直前にも再検証し、ログイン状態やグループ変更後の古いカートが制限を迂回しないようにします。
B2BCustomerContextとTwig向けのcanViewProduct()・canPurchaseProduct()は、現在ログイン中のストア会員を対象にします。管理画面の受注編集・受注再計算では、ログイン中の管理者ではなくOrderの注文者を判定する必要があります。$Order->getCustomer()をcanCustomerViewProduct()またはcanCustomerPurchaseProduct()へ明示的に渡してください。
次は、現在のストア会員を対象とするサービスの再利用方法を示す最小例です。実際にはカート・購入処理のイベントやProcessor内で、拒否結果をEC-CUBEのエラーとして返してください。
use Eccube\Entity\Product;
use Plugin\SimpleB2BPriceTierV2\Service\B2BAccessService;
final class EmployeeProductGuard
{
private B2BAccessService $b2bAccessService;
public function __construct(B2BAccessService $b2bAccessService)
{
$this->b2bAccessService = $b2bAccessService;
}
public function canPurchase(Product $Product): bool
{
return $this->b2bAccessService->canPurchaseProduct(
$Product,
[3],
['tag_ids' => [12]]
);
}
}
管理画面では現在のSecurityユーザーが管理者になるため、B2BCustomerContextで注文者グループを判定しないでください。対象商品の判定と、受注に紐づく会員の判定を明示的に組み合わせます。
$canPurchase = $this->b2bAccessService->canCustomerPurchaseProduct(
$Order->getCustomer(),
$Product,
[3],
['tag_ids' => [12]]
);
変更後の確認
バックアップ
変更するTwigファイルをバックアップします。
Twigを編集
使用中のテンプレート配下だけを変更します。
キャッシュ削除
管理画面またはCLIからEC-CUBEキャッシュを削除します。
権限別テスト
ゲスト・一般会員・許可グループ・非許可グループで確認します。
php bin/console cache:clear --env=prod
php bin/console cache:warmup --env=prod