Создание индивидуального платежа в Optimizely Commerce 14 (с помощью простого метода «Учетная запись») – PowerBuilder

Для этого процесса в Commerce 14 нет официальной пошаговой документации, поэтому я декомпилировал части платформы и опирался на (более старую) версию. Книги по коммерции чтобы собрать это воедино. В этом посте показан минимальный рабочий путь и объяснено только то, что вам нужно, без глубоких погружений.

Я взял за основу образец Foundation; несколько вспомогательных классов повторно используются из этого проекта и здесь не показаны. Вы можете скопировать недостающие помощники из репозитория Foundation: https://github.com/episerver/Foundation/tree/main

Что мы строим

Мы добавим очень простой Счет оплата:

  1. А Вариант оплаты который собирает AccountNumber и создает Payment объект для внесения в заказ.
  2. А Платежный шлюз который Optimizely вызывает во время обработки (авторизация/захват/возврат).
  3. А Конфигурация оплаты в интерфейсе администратора, где вы выбираете:
    • Имя класса → ваш класс шлюза (реализует IPaymentGateway)
    • Класс оплаты → бетон Payment подкласс, который возвращает ваш вариант (мы будем использовать OtherPayment чтобы было проще). «Класс платежа» явно является классом, который наследует Mediachase.Commerce.Orders.Payment.

1) Вариант оплаты — AccountPaymentOption

Это модель пользовательского интерфейса, которая отображает «Учетную запись» при оформлении заказа, проверяет вводимые данные и создает Payment.

using YourNamespace.Web.Infrastructure.Commerce.Markets;
using Mediachase.Commerce.Orders;
using Mediachase.Commerce.Orders.Managers;

namespace YourNamespace.Web.Features.Checkout.Payments
{
    public class AccountPaymentOption(
        LocalizationService localizationService,
        IOrderGroupFactory orderGroupFactory,
        ICurrentMarket currentMarket,
        LanguageService languageService,
        IPaymentService paymentService)
        : PaymentOptionBase(localizationService, orderGroupFactory, currentMarket, languageService, paymentService)
    {
        public override string SystemKeyword => "Account";

        public string AccountNumber { get; set; } = string.Empty;

        public override IPayment CreatePayment(decimal amount, IOrderGroup orderGroup)
        {
            var implementationClassName = PaymentManager.GetPaymentMethod(base.PaymentMethodId, false)
                .PaymentMethod[0].PaymentImplementationClassName;

            var type = Type.GetType(implementationClassName);
            var payment = type == null
                ? orderGroup.CreatePayment(OrderGroupFactory)
                : orderGroup.CreatePayment(OrderGroupFactory, type);

            payment.PaymentMethodId = PaymentMethodId;
            payment.PaymentMethodName = SystemKeyword;
            payment.Amount = amount;
            payment.PaymentType = PaymentType.Other;

            // Custom data for the gateway
            payment.Properties["AccountNumber"] = this.AccountNumber;

            return payment;
        }

        public override bool ValidateData()
        {
            // minimal validation; expand as needed
            return !string.IsNullOrWhiteSpace(AccountNumber);
        }
    }
}

Почему существует этот класс

Checkout нужен компонент для собирать данные и вернуть Payment возражать против OrderForm.Payments.

2) Платежный шлюз — AccountPaymentGateway

Это последняя точка интеграции между Optimizely Commerce и вашим платежным провайдером/логикой. Каждый новый тип платежа должен иметь шлюз.

using Mediachase.Commerce.Orders;
using Mediachase.Commerce.Plugins.Payment;

namespace YourNamespace.Web.Features.Checkout.Payments
{
    public class AccountPaymentGateway : AbstractPaymentGateway, IPaymentPlugin
    {
        public PaymentProcessingResult ProcessPayment(IOrderGroup orderGroup, IPayment payment)
        {
            if (string.IsNullOrEmpty(payment.Properties["AccountNumber"]?.ToString()))
            {
                return PaymentProcessingResult.CreateUnsuccessfulResult("Invalid account number.");
            }

            // Your custom business logic / external call would go here
            return PaymentProcessingResult.CreateSuccessfulResult("");
        }

        public override bool ProcessPayment(Payment payment, ref string message)
        {
            var result = ProcessPayment(null, payment);
            message = result.Message;
            return result.IsSuccessful;
        }
    }
}

Почему существует этот класс

Шлюзы реализуют обработка сторона (авторизация/захват/возврат).

Read more:  «Это активизм»: Марджори Тейлор Грин поддерживает арест Дона Лемона в связи с церковным протестом в Миннесоте

3) Подключение (DI) и настройка администратора

Зарегистрируйте способ оплаты, чтобы ваша касса могла решить эту проблему:

// in an initialization module
context.Services.AddTransient();

Затем в Настройки → Платежи добавить новый платеж:

  • Имя класса → ваш полностью квалифицированный AccountPaymentGateway
  • Класс оплатыMediachase.Commerce.Orders.OtherPayment
  • Рынок → Рынок по умолчанию

Почему эти два поля важны:

  • Имя класса должен указывать на ваш шлюз (реализация IPaymentGateway).
  • Класс оплаты должен быть конкретным подклассом Payment что ваш вариант возвращается.

4) (Необязательно, дополнительно) Сохранить AccountNumber в своей таблице

⚠️ Не рекомендуется, если вам действительно не нужен такой первоклассный стол, как OrderFormPayment_CreditCard. В Commerce 14 удален старый Commerce Manager, который использовался для их создания; теперь ты должен сделать это сам. Продолжайте, только если вы понимаете риски, связанные с обновлениями и обслуживанием.

4.1 Создание бетона Payment подкласс

using System.Runtime.Serialization;
using Mediachase.Commerce.Orders;
using Mediachase.MetaDataPlus.Configurator;

namespace YourNamespace.Web.Features.Checkout.Payments
{
    [Serializable]
    public class AccountPayment : Payment
    {
        public string AccountNumber
        {
            get => this.GetString(nameof(AccountNumber));
            set => this[nameof(AccountNumber)] = value;
        }

        private static MetaClass? _metaClass;

        public static MetaClass GenericAccountMetaClass
        {
            get
            {
                _metaClass ??= MetaClass.Load(OrderContext.MetaDataContext, nameof(AccountPayment));
                return _metaClass;
            }
        }

        public AccountPayment() : base(GenericAccountMetaClass, GetDefaultValue) { }

        private static object? GetDefaultValue(string fieldName)
            => "PaymentType".Equals(fieldName) ? PaymentType.Other : null;

        public AccountPayment(SerializationInfo info, StreamingContext context) : base(info, context)
        {
            this.PaymentType = PaymentType.Other;
            this.ImplementationClass = GetType().AssemblyQualifiedName;
        }
    }
}

Если вы используете этот класс, смените администратора Класс оплаты к YourNamespace.Web.Features.Checkout.Payments.AccountPayment

4.2 Создайте резервные таблицы

Запустите это точный сценарий в вашей базе данных Commerce для создания таблиц и FK:

CREATE TABLE [dbo].[OrderFormPayment_Account](
	[ObjectId] [int] NOT NULL,
	[CreatorId] [nvarchar](100) NULL,
	[Created] [datetime] NULL,
	[ModifierId] [nvarchar](100) NULL,
	[Modified] [datetime] NULL,
	[AccountNumber] [nvarchar](512) NULL,
 CONSTRAINT [PK_OrderFormPayment_Account] PRIMARY KEY CLUSTERED 
(
	[ObjectId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[OrderFormPayment_Account]  WITH CHECK ADD  CONSTRAINT [FK_OrderFormPayment_Account_OrderFormPayment] FOREIGN KEY([ObjectId])
REFERENCES [dbo].[OrderFormPayment] ([PaymentId])
ON UPDATE CASCADE
ON DELETE CASCADE
GO

ALTER TABLE [dbo].[OrderFormPayment_Account] CHECK CONSTRAINT [FK_OrderFormPayment_Account_OrderFormPayment]
GO

CREATE TABLE [dbo].[OrderFormPayment_Account_Localization](
	[Id] [int] IDENTITY(1,1) NOT NULL,
	[ObjectId] [int] NOT NULL,
	[ModifierId] [nvarchar](100) NULL,
	[Modified] [datetime] NULL,
	[Language] [nvarchar](20) NOT NULL,
	[AccountNumber] [nvarchar](512) NULL,
 CONSTRAINT [PK_OrderFormPayment_Account_Localization] PRIMARY KEY CLUSTERED 
(
	[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[OrderFormPayment_Account_Localization]  WITH CHECK ADD  CONSTRAINT [FK_OrderFormPayment_Account_Localization_OrderFormPayment] FOREIGN KEY([ObjectId])
REFERENCES [dbo].[OrderFormPayment] ([PaymentId])
ON UPDATE CASCADE
GO

ALTER TABLE [dbo].[OrderFormPayment_Account_Localization] CHECK CONSTRAINT [FK_OrderFormPayment_Account_Localization_OrderFormPayment]
GO

4.3 Регистрация метакласса и отношений полей

Этот поднимает настроение а MetaClass названный AccountPaymentдобавляет MetaField называется AccountNumberотражает стандартные поля, используемые CashCard, и связывает ваше новое поле:

BEGIN TRY
    BEGIN TRAN;

    ----------------------------------------------------------------------
    -- 1) Upsert MetaClass: AccountPayment
    ----------------------------------------------------------------------
    DECLARE @NewMetaClassId int;

    SELECT @NewMetaClassId = mc.MetaClassId
    FROM MetaClass mc
    WHERE mc.[Namespace] = N'Mediachase.Commerce.Orders.System'
      AND mc.[Name]      = N'AccountPayment';

    IF @NewMetaClassId IS NULL
    BEGIN
        DECLARE @tNewClass table (MetaClassId int);

        INSERT INTO MetaClass
            (Namespace, Name, FriendlyName, IsSystem, IsAbstract, ParentClassId, TableName, PrimaryKeyName, [Description])
        OUTPUT INSERTED.MetaClassId INTO @tNewClass(MetaClassId)
        SELECT TOP (1)
            N'Mediachase.Commerce.Orders.System',
            N'AccountPayment',
            N'Account Payment',
            0,
            0,
            10,
            N'OrderFormPayment_Account',
            N'PK_OrderFormPayment_Account',
            N'Account payment'
        FROM MetaClass
        WHERE Name LIKE N'CashCard%';

        SELECT @NewMetaClassId = MetaClassId FROM @tNewClass;
    END;

    IF @NewMetaClassId IS NULL
    BEGIN
        INSERT INTO MetaClass
            (Namespace, Name, FriendlyName, IsSystem, IsAbstract, ParentClassId, TableName, PrimaryKeyName, [Description])
        VALUES
            (N'Mediachase.Commerce.Orders.System',
             N'AccountPayment',
             N'Account Payment',
             0, 0, 10,
             N'OrderFormPayment_Account',
             N'PK_OrderFormPayment_Account',
             N'Account payment');

        SET @NewMetaClassId = SCOPE_IDENTITY();
    END;

    ----------------------------------------------------------------------
    -- 2) Upsert MetaField: AccountNumber
    ----------------------------------------------------------------------
    DECLARE @NewMetaFieldId int;

    SELECT @NewMetaFieldId = mf.MetaFieldId
    FROM MetaField mf
    WHERE mf.[Name] = N'AccountNumber'
      AND mf.[Namespace] = N'Mediachase.Commerce.Orders.System';

    IF @NewMetaFieldId IS NULL
    BEGIN
        INSERT INTO MetaField
            (Name, Namespace, SystemMetaClassId, FriendlyName, [Description],
             DataTypeId, [Length], AllowNulls, MultiLanguageValue, AllowSearch, IsEncrypted, IsKeyField)
        VALUES
            (N'AccountNumber', N'Mediachase.Commerce.Orders.System', 0,
             N'Cash Card Number', N'Contains cash card number',
             31, 512, 1, 0, 0, 0, 0);

        SET @NewMetaFieldId = SCOPE_IDENTITY();
    END;

    ----------------------------------------------------------------------
    -- 3) Relations: MetaClassMetaFieldRelation
    ----------------------------------------------------------------------
    -- 3a) Mirror fields related to any *CashCard* class (and SystemMetaClassId = 10) onto the new class
    INSERT INTO MetaClassMetaFieldRelation (MetaClassId, MetaFieldId, [Weight], Enabled)
    SELECT DISTINCT
        @NewMetaClassId,
        f.MetaFieldId,
        0,
        1
    FROM MetaField f
    INNER JOIN MetaClassMetaFieldRelation r ON f.MetaFieldId = r.MetaFieldId
    INNER JOIN MetaClass c ON c.MetaClassId = r.MetaClassId
    WHERE c.[Name] LIKE N'%CashCard%'
      AND f.SystemMetaClassId = 10
      AND NOT EXISTS (
            SELECT 1
            FROM MetaClassMetaFieldRelation r2
            WHERE r2.MetaClassId = @NewMetaClassId
              AND r2.MetaFieldId = f.MetaFieldId
        );

    -- 3b) Ensure the new "AccountNumber" field is related to the new class
    IF NOT EXISTS (
        SELECT 1
        FROM MetaClassMetaFieldRelation
        WHERE MetaClassId = @NewMetaClassId
          AND MetaFieldId = @NewMetaFieldId
    )
    BEGIN
        INSERT INTO MetaClassMetaFieldRelation (MetaClassId, MetaFieldId, [Weight], Enabled)
        VALUES (@NewMetaClassId, @NewMetaFieldId, 0, 1);
    END;

    -- 3c) Run the metadata proc generator for your new class
    EXEC mdpsp_sys_CreateMetaClassProcedure @NewMetaClassId;

    COMMIT TRAN;
END TRY
BEGIN CATCH
    IF XACT_STATE() <> 0 ROLLBACK TRAN;
    DECLARE @Err nvarchar(2048) = ERROR_MESSAGE();
    RAISERROR('Provisioning AccountPayment failed: %s', 16, 1, @Err);
END CATCH;
GO

4.4 Удаление крючка в ecf_OrderForm_Delete

Этот шаг изменяет стандартную процедуру для удаления ваших пользовательских строк. Не рекомендуется, но если вы зеркалируете устаревший шаблон, вам необходимо добавить вызов процедуры удаления:

-- inside the "Delete payments" cursor loop:
EXEC [dbo].[mdpsp_avto_OrderFormPayment_Account_Delete] @TempObjectId;
-- (the built-in ones are already there:)
EXEC [dbo].[mdpsp_avto_OrderFormPayment_CashCard_Delete]  @TempObjectId;
EXEC [dbo].[mdpsp_avto_OrderFormPayment_CreditCard_Delete] @TempObjectId;
EXEC [dbo].[mdpsp_avto_OrderFormPayment_GiftCard_Delete]   @TempObjectId;
EXEC [dbo].[mdpsp_avto_OrderFormPayment_Invoice_Delete]    @TempObjectId;
EXEC [dbo].[mdpsp_avto_OrderFormPayment_Other_Delete]      @TempObjectId;
EXEC [dbo].[mdpsp_avto_OrderFormPayment_Exchange_Delete]   @TempObjectId;

Читайте также

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.