punchout-catalogs/punchout-catalog-spryker

Spryker电子商务平台Punchout目录模块


README

Spryker电子商务平台Punchout目录模块

重要变更

从2.4.0版本开始,更改了Punchout连接加载的方式。

#1.业务单元在网关URL(入口点)中变为可选。

#2.在cXML设置请求中需要输入买家ID,格式如下:Credential_Domain_Value/Credential_Identity_Value

示例 #1: AribaNetworkId/AN119990XX

示例 #2: NetworkId/NID119990XX

安装

composer require punchout-catalogs/punchout-catalog-spryker

B2C商店还需要spryker-feature/company-account功能。

查看etc/integration-sample/*.patch补丁作为与Spryker B2BB2C 示例商店集成的示例。

文档

集成文档

Spryker文档

测试

运行

./vendor/bin/codecept run

自定义购物车映射

可以通过重写PunchoutCatalog\Yves\PunchoutCatalog\PunchoutCatalogConfig类的方法来实现扩展购物车映射行为

<?php

namespace Pyz\Yves\PunchoutCatalog;

use PunchoutCatalog\Yves\PunchoutCatalog\PunchoutCatalogConfig as BasePunchoutCatalogConfig;

class PunchoutCatalogConfig extends BasePunchoutCatalogConfig
{
    /**
     * @return array
     */
    public function getCustomCartMapping(): array
    {
        return [
            // QuoteTransfer => PunchoutCatalogDocumentCartTransfer

            // without key, should return transfer object
            function ($quoteTransfer, $cartRequestTransfer, $plugin) {
                $cartRequestTransfer->setCoupon('Coupon for ' . $quoteTransfer->getName());
                return $cartRequestTransfer;
            },
            'cart_note' => 'name',
        ];
    }

    /**
     * @return array
     */
    public function getCustomCartItemMapping(): array
    {
        return [
            //ItemTransfer => PunchoutCatalogDocumentCartItemTransfer
            'custom_sku' => function($quoteItemTransfer, $documentCartItemTransfer, $quoteTransfer, $plugin) {
                return 'here-is-custom-sku-' . $quoteItemTransfer->getAbstractSku();
            },

            'sale_bunch_quantity' => function($quoteItemTransfer, $documentCartItemTransfer, $quoteTransfer, $plugin)  {
                //Product #1
                if ($quoteItemTransfer->getAbstractSku() === 'any_condition_1') {
                    return 100;
                }
                //Product #2
                if ($quoteItemTransfer->getAbstractSku() === 'any_condition_2') {
                    return  50;
                }
                return 1;
            },

            'custom_fields' => function($quoteItemTransfer, $documentCartItemTransfer, $quoteTransfer, $plugin) {
                return array(
                    'custom_field_1' => 'quote-item-id=' . $quoteItemTransfer->getId(),
                    'custom_field_2' => 'custom-abstract-sku-' . $quoteItemTransfer->getAbstractSku(),
                    'custom_field_3' => 'custom_field_value_3',
                    'custom_field_4' => 'custom_field_value_4_' . uniqid(),
                    'custom_field_5' => 'custom_field_value_5_' . uniqid(),
                    'custom_field_contract' => 'ContractID-'. uniqid(),
                    'custom_field_org' => 'TestPurchOrg',
                    'custom_field_ref' => 'some-ref',
                    //...add as many custom fields as you need and can use in mapping
                );
            },

            /**
             * @param \Generated\Shared\Transfer\ItemTransfer
             * @param \Generated\Shared\Transfer\PunchoutCatalogDocumentCartItemTransfer
             * @param \Generated\Shared\Transfer\QuoteTransfer
             * @param \PunchoutCatalog\Yves\PunchoutCatalog\Mapper\CartTransferMapperDefaultPlugin
             */
            function ($quoteItemTransfer, $documentCartItemTransfer, $quoteTransfer, $plugin) {
                $name = trim($quoteItemTransfer->getName());
                $documentCartItemTransfer->setDiscountDescription('Custom discount description for ' . $name);
                return $documentCartItemTransfer;
            },
            'discount_description' => 'name',
            'cart_note' => 'group_key',
        ];
    }

    /**
     * @return array
     */
    public function getCustomCartCustomerMapping(): array
    {
        return [
            //CustomerTransfer => PunchoutCatalogDocumentCartCustomerTransfer

            'first_name' => 'customer_reference',

            /**
             * @param \Generated\Shared\Transfer\CustomerTransfer
             * @param \Generated\Shared\Transfer\PunchoutCatalogDocumentCustomerTransfer
             * @param \Generated\Shared\Transfer\QuoteTransfer
             * @param \PunchoutCatalog\Yves\PunchoutCatalog\Mapper\CartTransferMapperDefaultPlugin
             */
            function ($quoteCustomerTransfer, $documentCartCustomerTransfer, $quoteTransfer, $plugin) {
                return $documentCartCustomerTransfer;
            },
        ];
    }
}

如果这个机会还不够,您可以定义自己的插件,该插件应实现PunchoutCatalog\Yves\PunchoutCatalog\Mapper\CartTransferMapperPluginInterface,并通过重写PunchoutCatalog\Yves\PunchoutCatalog\PunchoutCatalogDependencyProvider::getCartTransferMapperPlugins方法来添加它。

以新方式启用Yves控制器(自Spryker版本202001以来)

src/Pyz/Yves/Router/RouterDependencyProvider.php中启用Yves Punchout路由

<?php

namespace Pyz\Yves\Router;

use PunchoutCatalog\Yves\PunchoutCatalog\Plugin\Router\PunchoutCatalogRouteProviderPlugin;
use Spryker\Yves\Router\RouterDependencyProvider as SprykerRouterDependencyProvider;

class RouterDependencyProvider extends SprykerRouterDependencyProvider
{
    /**
     * @return \Spryker\Yves\RouterExtension\Dependency\Plugin\RouteProviderPluginInterface[]
     */
    protected function getRouteProvider(): array
    {
        return [
            new PunchoutCatalogRouteProviderPlugin()
        ];
    }
}

以旧方式启用Yves控制器(Spryker版本202001之前)

src/Pyz/Yves/ShopApplication/YvesBootstrap.php中注册Punchout路由

<?php

namespace Pyz\Yves\ShopApplication;

use PunchoutCatalog\Yves\PunchoutCatalog\Plugin\Provider\PunchoutCatalogControllerProvider;
use SprykerShop\Yves\ShopApplication\YvesBootstrap as SprykerYvesBootstrap;

class YvesBootstrap extends SprykerYvesBootstrap
{
    /**
     * @param bool|null $isSsl
     *
     * @return \SprykerShop\Yves\ShopApplication\Plugin\Provider\AbstractYvesControllerProvider[]
     */
    protected function getControllerProviderStack($isSsl)
    {
        return [
            new PunchoutCatalogControllerProvider($isSsl),
        ];
    }
}

以新方式启用Zed控制器(自Spryker版本202001以来)

src/Pyz/Zed/Router/RouterConfig.php中启用Zed Punchout路由

<?php

namespace Pyz\Zed\Router;

use Spryker\Zed\Router\RouterConfig as SprykerRouterConfig;

class RouterConfig extends SprykerRouterConfig
{
    /**
     * @return string[]
     */
    public function getControllerDirectories(): array
    {
        $controllerDirectories = parent::getControllerDirectories();

        //...
        $controllerDirectories[] = sprintf('%s/punchout-catalogs/*/src/*/Zed/*/Communication/Controller/', APPLICATION_VENDOR_DIR);

        return array_filter($controllerDirectories, 'glob');
    }
}

故障排除

创建认证令牌错误的问题

如果spy_oauth_access_token.user_identifier字段太小,无法存储字段中的数据,则可能发生punchout-catalog.error.auth.token.create错误。默认为varchar(1024)

解决方案

最简单的方法是将其从varchar(1024)升级到LONGVARCHAR

创建方案文件 #1: src/Pyz/Zed/PunchoutCatalog/Persistence/Propel/Schema/spy_oauth.schema.xml

<?xml version="1.0"?>
<database xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          name="zed"
          xsi:noNamespaceSchemaLocation="http://static.spryker.com/schema-01.xsd"
          namespace="Orm\Zed\Oauth\Persistence"
          package="src.Orm.Zed.Oauth.Persistence">

    <table name="spy_oauth_access_token">
        <column name="user_identifier" type="LONGVARCHAR"/>
    </table>

</database>

创建方案文件 #2: src/Pyz/Zed/PunchoutCatalog/Persistence/Propel/Schema/spy_oauth_revoke.schema.xml

<?xml version="1.0"?>
<database xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          name="zed"
          xsi:noNamespaceSchemaLocation="http://static.spryker.com/schema-01.xsd"
          namespace="Orm\Zed\OauthRevoke\Persistence"
          package="src.Orm.Zed.OauthRevoke.Persistence">

    <table name="spy_oauth_refresh_token">
        <column name="user_identifier" type="LONGVARCHAR"/>
    </table>

</database>

数据库升级

vendor/bin/console propel:install

数据库模式定义

在管理员面板中消失的PunchOut菜单项问题(与spryker-eco/punchout-catalogs相关)

可能原因

使用BREADCRUMB_MERGE_STRATEGY隐藏了所有未在config/Zed/navigation.xml文件中定义的自定义菜单项。请参阅:https://docs.spryker.com/docs/scos/dev/back-end-development/extending-spryker/adding-navigation-in-the-back-office.html#defining-a-navigation-merge-strategysrc/Pyz/Zed/ZedNavigation/ZedNavigationConfig.php文件中定义的策略。

解决方案

通过在config/Zed/navigation.xml文件中添加以下代码轻松恢复BREADCRUMB_MERGE_STRATEGY的菜单项

    <punchout-catalogs>
        <label>PunchOut</label>
        <title>PunchOut</title>
        <pages>
            <connection>
                <label>Connections</label>
                <title>Connections</title>
                <bundle>punchout-catalogs</bundle>
                <controller>index</controller>
                <action>index</action>
                <visible>1</visible>
            </connection>
            <transaction-log>
                <label>Transactions Log</label>
                <title>Transactions Log</title>
                <bundle>punchout-catalogs</bundle>
                <controller>transaction</controller>
                <action>index</action>
                <visible>1</visible>
            </transaction-log>
        </pages>
    </punchout-catalogs>

如果导航菜单已缓存(商店以production模式运行),请运行application:build-navigation-cache命令。

具有许多自定义字段的OCI购物车映射示例

{
    "cart_item": {
        "fields": {
            "quantity": {
                "path": "NEW_ITEM-QUANTITY[%line_number%]"
            },
            "internal_id": {
                "path": "NEW_ITEM-EXT_PRODUCT_ID[%line_number%]"
            },
            "parent_line_number": {
                "path": "NEW_ITEM-PARENT_ID[%line_number%]"
            },
            "item_type": {
                "path": "NEW_ITEM-ITEM_TYPE[%line_number%]",
                "transform":
                [
                    {
                        "map": {
                            "value": "composite",
                            "result": "R"
                        }
                    },
                    {
                        "map": {
                            "value": "item",
                            "result": "O"
                        }
                    }
                ]
            },
            "sku": {
                "path": "NEW_ITEM-VENDORMAT[%line_number%],NEW_ITEM-MANUFACTMAT[%line_number%]"
            },
            "currency": {
                "path": "NEW_ITEM-CURRENCY[%line_number%]"
            },
            "unit_total": {
                "path": "NEW_ITEM-PRICE[%line_number%]"
            },
            "name": {
                "path": "NEW_ITEM-DESCRIPTION[%line_number%]",
                "transform": [{
                    "cut": {
                        "len": "40"
                    }
                }]
            },
            "long_description": {
                "path": "NEW_ITEM-LONGTEXT_%line_number%:132[]"
            },
            "uom": {
                "path": "NEW_ITEM-UNIT[%line_number%]",
                "transform": [{
                    "default": {
                        "value": "EA"
                    }
                }]
            },
            "unspsc": {
                "path": "NEW_ITEM-MATGROUP[%line_number%]"
            },
            "supplier_id": {
                "path": "NEW_ITEM-VENDOR[%line_number%]"
            },
            "sale_bunch_quantity": {
                "path": "NEW_ITEM-PRICEUNIT[%line_number%]"
            },
            "custom_fields/custom_field_org": {
                "path": "NEW_ITEM-PURCHORG[%line_number%]"
            },
            "custom_fields/custom_field_ref": {
                "path": "NEW_ITEM-PURCHINFREC[%line_number%]",
                "transform": [
                    "uppercase"
                ]
            },
            "custom_fields/custom_field_contract": {
                "path": "NEW_ITEM-CONTRACT[%line_number%]",
                "transform": [
                    "lowercase"
                ]
            },
            "custom_fields/custom_field_1": {
                "path": "NEW_ITEM-CUSTFIELD1[%line_number%]"
            },
            "custom_fields/custom_field_2": {
                "path": "NEW_ITEM-CUSTFIELD2[%line_number%]"
            },
            "custom_fields/custom_field_3": {
                "path": "NEW_ITEM-CUSTFIELD3[%line_number%]"
            },
            "custom_fields/custom_field_4": {
                "path": "NEW_ITEM-CUSTFIELD4[%line_number%]"
            },
            "custom_fields/custom_field_5": {
                "path": "NEW_ITEM-CUSTFIELD5[%line_number%]"
            }
        }
    }
}

OCI购物车映射示例,包含集成销售批量数量(以及新的项目-价格单位)

{
    "cart_item": {
        "fields": {
            ...
            "quantity": {
                "path": "NEW_ITEM-QUANTITY[%line_number%]"
            },
            "sale_bunch_quantity": {
                "path": "NEW_ITEM-PRICEUNIT[%line_number%]",
                "transform": [
                    "round"
                ]
            },
            "unit_total": {
                "path": "NEW_ITEM-PRICE[%line_number%]"
            }
        }
    }
}

OCI购物车映射示例,包含销售数量(不包含新的项目-价格单位,并分摊价格)

sale_quantity = 数量 / 销售批量数量

{
    "cart_item": {
        "fields": {
            ...
            "sale_quantity": {
                "path": "NEW_ITEM-QUANTITY[%line_number%]"
            },
            "sale_bunch_quantity_unit_total": {
                "path": "NEW_ITEM-PRICE[%line_number%]",
                "transform": [
                    {"round":  {"precision":  "3"}}
                ]
            }
        }
    }
}

cXML购物车映射示例

{
  "cart": {
    "fields": {
      "grand_total": {
        "path": "/cXML/Message[1]/PunchOutOrderMessage[1]/PunchOutOrderMessageHeader[1]/Total[1]/Money[1]"
      },

      "tax_total": {
        "path": "/cXML/Message[1]/PunchOutOrderMessage[1]/PunchOutOrderMessageHeader[1]/Tax[1]/Money[1]"
      },
      "tax_description": {
        "path": "/cXML/Message[1]/PunchOutOrderMessage[1]/PunchOutOrderMessageHeader[1]/Tax[1]/Description[1]"
      },

      "discount_total": {
        "path": "/cXML/Message[1]/PunchOutOrderMessage[1]/PunchOutOrderMessageHeader[1]/Discount[1]/Money[1]"
      },
      "discount_description": {
        "path": "/cXML/Message[1]/PunchOutOrderMessage[1]/PunchOutOrderMessageHeader[1]/Discount[1]/Description[1]"
      },

      "currency": {
        "path": "/cXML/Message[1]/PunchOutOrderMessage[1]/PunchOutOrderMessageHeader[1]/Total[1]/Money[1]/@currency,/cXML/Message[1]/PunchOutOrderMessage[1]/PunchOutOrderMessageHeader[1]/Tax[1]/Money[1]/@currency,/cXML/Message[1]/PunchOutOrderMessage[1]/PunchOutOrderMessageHeader[1]/Discount[1]/Money[1]/@currency",
        "append": true
      },
                  "cart_note": {
        "path": "/cXML/Message[1]/PunchOutOrderMessage[1]/PunchOutOrderMessageHeader[1]/Comments[1]"
      }
    }
  },
  "cart_item": {
    "fields": {
      "line_number": {
        "path": "@lineNumber"
      },
                  "parent_line_number": {
        "path": "@parentLineNumber"
      },
                  "item_type": {
        "path": "@itemType"
      },
                  "composite_item_type": {
        "path": "@compositeItemType"
      },
      "quantity": {
        "path": "@quantity"
      },
      "internal_id": {
        "path": "ItemID[1]/SupplierPartAuxiliaryID[1]"
      },
      "sku": {
        "path": "ItemID[1]/SupplierPartID[1],ItemDetail[1]/BuyerPartID[1],ItemDetail[1]/ManufacturerPartID[1]"
      },
      "unit_total": {
        "path": "ItemDetail[1]/UnitPrice[1]/Money[1]"
      },
      "currency": {
        "path": "ItemDetail[1]/UnitPrice[1]/Money[1]/@currency"
      },
      "name": {
        "path": "ItemDetail[1]/Description[1]/ShortName"
      },
      "long_description": {
        "path": "ItemDetail[1]/Description[1]"
      },
      "uom": {
        "path": "ItemDetail[1]/UnitOfMeasure[1]",
        "transform": [{
          "default": {
            "value": "EA"
          }
        }]
      },
      "brand": {
        "path": "ItemDetail[1]/ManufacturerName[1]"
      },
      "supplier_id": {
        "path": "ItemDetail[1]/SupplierID[1]"
      },
      "cart_note": {
        "path": "ItemDetail[1]/Comments[1]"
      },
      "image_url": {
        "path": "ItemDetail[1]/Extrinsic[@name='ImageURL']"
      },
      "locale": {
        "path": "ItemDetail[1]/Description[1]/@xml:lang"
      },
      "options": {
        "path": "ItemDetail[1]/Extrinsic/customOption()",
        "multiple": true
      }
    }
  },
  "customOption": {
    "fields": {
      "code": {
        "path": "@name"
      },
      "value": {
        "path": "./"
      }
    }
  }
}

cXML请求映射示例

{
  "customer": {
    "fields": {
      "first_name": {
        "path": "/cXML/Request[1]/PunchOutSetupRequest[1]/Extrinsic[@name='FirstName']"
      },
      "last_name": {
        "path": "/cXML/Request[1]/PunchOutSetupRequest[1]/Extrinsic[@name='LastName']"
      },
      "email": {
        "path": "/cXML/Request[1]/PunchOutSetupRequest[1]/Extrinsic[@name='UserEmail']"
      }
    }
  },
  "cart_item": {
    "fields": {
      "internal_id":{
        "path": "/cXML/Request[1]/PunchOutSetupRequest[1]/ItemOut/ItemID[1]/SupplierPartAuxiliaryID"
      }
    }
  }
}