AcumaticaERP PluginDevelopmentGuide
AcumaticaERP PluginDevelopmentGuide
Contents
Copyright...............................................................................................................................................4
Plug-In Development Guide.....................................................................................................................5
Creating Widgets for Dashboards.............................................................................................................6
Widget Creation................................................................................................................................................. 6
Use of the Widgets.............................................................................................................................................7
To Create a Simple Widget................................................................................................................................ 9
To Create an Inquiry-Based Widget................................................................................................................ 11
To Load a Widget Synchronously or Asynchronously....................................................................................13
To Add a Script to a Widget.............................................................................................................................14
To Add Custom Controls to the Widget Properties Dialog Box..................................................................... 14
Customizing Business Events in Code..................................................................................................... 17
To Define a Custom Subscriber Type for Business Events............................................................................ 17
Implementing Plug-Ins for Processing Credit Card Payments................................................................... 23
Interfaces for Processing Credit Card Payments............................................................................................23
To Implement a Plug-In for Processing Credit Card Payments..................................................................... 24
Implementing a Connector for an E-Commerce System............................................................................28
Architecture of a Commerce Connector.........................................................................................................28
Classes for Acumatica ERP Entities....................................................................................................... 29
Classes for External Entities...................................................................................................................30
Mapping Classes..................................................................................................................................... 32
Bucket Classes........................................................................................................................................ 33
Processor Classes................................................................................................................................... 34
Connector Class...................................................................................................................................... 41
Processor Factory Class......................................................................................................................... 44
Connector Descriptor Class....................................................................................................................45
Connector Factory Class........................................................................................................................ 46
Use of a Commerce Connector....................................................................................................................... 47
Detection of the Connector in an Application...................................................................................... 47
Establishment of the Connection with the External System................................................................48
Preparation of Data for the Synchronization........................................................................................ 49
Synchronization of the Prepared Data.................................................................................................. 51
Real-Time Synchronization Through Push Notifications..................................................................... 55
To Create a Connector for an External System.............................................................................................. 56
Step 1: Creating an Extension Library for Acumatica ERP....................................................................56
Contents | 3
Copyright
No part of this document may be reproduced, copied, or transmitted without the express prior consent of
Acumatica, Inc.
3933 Lake Washington Blvd NE, # 350, Kirkland, WA 98033
Restricted Rights
The product is provided with restricted rights. Use, duplication, or disclosure by the United States Government is
subject to restrictions as set forth in the applicable License and Services Agreement and in subparagraph (c)(1)(ii)
of the Rights in Technical Data and Computer Soware clause at DFARS 252.227-7013 or subparagraphs (c)(1) and
(c)(2) of the Commercial Computer Soware-Restricted Rights at 48 CFR 52.227-19, as applicable.
Disclaimer
Acumatica, Inc. makes no representations or warranties with respect to the contents or use of this document, and
specifically disclaims any express or implied warranties of merchantability or fitness for any particular purpose.
Further, Acumatica, Inc. reserves the right to revise this document and make changes in its content at any time,
without obligation to notify any person or entity of such revisions or changes.
Trademarks
Acumatica is a registered trademark of Acumatica, Inc. HubSpot is a registered trademark of HubSpot, Inc.
Microso Exchange and Microso Exchange Server are registered trademarks of Microso Corporation. All other
product names and services herein are trademarks or service marks of their respective companies.
Widget Creation
When you create a custom widget to be used in Acumatica ERP or an Acumatica Framework-based application, you
must implement at least the three classes described in the sections below. (For details on the implementation of
the classes, see To Create a Simple Widget and To Create an Inquiry-Based Widget.)
In this topic, you will learn how a custom widget is used in Acumatica ERP or an Acumatica Framework-based
application. For more information on how to work with widgets on a dashboard page, see Configuring Widgets.
The following diagram illustrates the interaction of the components of the widget with a dashboard page. In the
diagram, the items that you add for the custom widget are shown in rectangles with a red border.
Related Links
• Designing Dashboard Contents
• Administering Dashboard Forms
• Configuring Widgets
Creating Widgets for Dashboards | 9
A simple widget displays information from a single data source and does not require calculations or comprehensive
data retrieval. Multiple simple widgets, such as wiki widgets or embedded page widgets, are available in Acumatica
ERP or an Acumatica Framework-based application by default.
To create a simple widget, you need to perform the basic steps that are described in this topic.
using [Link];
[PXHidden]
public class YFSettings : IBqlTable
{
#region PagePath
[PXDBString]
[PXDefault]
[PXUIField(DisplayName = "Help Article")]
public string PagePath { get; set; }
public abstract class pagePath : IBqlField { }
#endregion
}
2. In the project, create a graph for working with the parameters of the widget and reading the data for the
widget. The graph must be inherited from the [Link] class.
The following code fragment shows an example of a graph for a widget.
using [Link];
3. In the project, create a widget class that implements the [Link] interface.
Use the following instructions for implementation:
• Inherit the widget class from the [Link] abstract class. This
class implements part of the required functionality of the IDashboardWidget interface, such as
localization of the caption and the description of the widget (which are displayed in the Add Widget
dialog box when a user adds a new widget to a dashboard). This class also stores useful properties of the
widget, such as Settings, DataGraph, Page, DataSource, and WidgetID.
• Store the values of the caption and the description of the widget in a Messages class that has the
PXLocalizable attribute. This approach is required for localization functionality to work properly.
• Perform initialization of a widget class instance in the Initialize() method of the
IDashboardWidget interface.
• Create the tree of controls of the widget in the Render() method of the IDashboardWidget
interface.
Creating Widgets for Dashboards | 10
• If you need to check the access rights of a user to the data displayed in the widget, implement the
IsAccessible() method of the IDashboardWidget interface. If the user has access to the data
in the widget, the method must return true; if the user has insufficient rights to access the data in the
widget, the method must return false.
• If you want to specify the way the widget is loaded, override the AsyncLoading() method of the
PXWidgetBase abstract class, as described in To Load a Widget Synchronously or Asynchronously.
The following code fragment gives an example of a widget class. This class is inherited from the
PXWidgetBase class. The caption and description of the widget are specified in the Messages class,
which has PXLocalizable attribute. The widget class implements the Render() method to create the
control of the widget and performs the configuration of the control in the RenderComplete() method.
using [Link];
using [Link];
using [Link];
using [Link];
[PXLocalizable]
public static class Messages
{
public const string YFWidgetCaption = "YogiFon Help Page";
public const string YFWidgetDescription = "Displays a Help page.";
Creating Widgets for Dashboards | 11
Related Links
• To Load a Widget Synchronously or Asynchronously
An inquiry widget retrieves data for the widget from an inquiry form, such as a generic inquiry form or a custom
inquiry form. Inquiry-based widgets that are available in Acumatica ERP or an Acumatica Framework-based
application by default include chart widgets, table widgets, and KPI widgets.
To create an inquiry-based widget, you need to perform the basic steps that are described in the section below. In
these steps, you use the predefined classes, which provide the following functionality to simplify the development
of an inquiry-based widget:
• Selection of the inquiry form in the widget settings
• Selection of a shared inquiry filter
• Selection of the parameters of the inquiry
• The ability to drill down in the inquiry form when a user clicks on the widget caption
• Verification of the user's access rights to the widget, which is performed based on the user's access rights to
the inquiry form
using [Link];
using [Link];
[PXHidden]
public class TableSettings : InquiryBasedWidgetSettings, IBqlTable
{
#region AutoHeight
[PXDBBool]
[PXDefault(true)]
[PXUIField(DisplayName = "Automatically Adjust Height")]
public bool AutoHeight { get; set; }
public abstract class autoHeight : IBqlField { }
#endregion
Creating Widgets for Dashboards | 12
...
}
2. In the project, create a graph for working with widget parameters and reading data for the widget. Use the
following instructions when you implement the graph:
• Inherit the graph from the [Link] abstract
class, which is inherited from the PXWidgetBase abstract class.
• Implement the SettingsRowSelected() event, which is the RowSelected event for the DAC with
widget parameters; it contains the current values of the parameters of the widget instance and the list of
available fields of the inquiry form. (For information on how to work with the fields of the inquiry form,
see To Use the Parameters and Fields of the Inquiry Form in the Widget.) The signature of the method is
shown below.
protected virtual void SettingsRowSelected(PXCache cache,
TPrimary settings, InqField[] inqFields)
3. In the project, create a widget class. We recommend that you inherit this class from the
[Link] class.
4. Compile your Acumatica ERP extension library or Acumatica Framework-based application.
5. Run the application, and make sure that the new widget appears in the Add Widget dialog box. The widget
class, which implements the IDashboardWidget interface, is detected by the system and automatically
added to the list of widgets available for selection in the dialog box.
To Use the Parameters and Fields of the Inquiry Form in the Widget
You can access the parameters and fields of the inquiry from that is used in the widget by using the
DataScreenBase class, which is available through the DataScreen property in the widget graph and in the
widget class. An instance of the DataScreenBase class, which is created based on the inquiry form selected by a
user in the widget settings, contains the following properties:
• ViewName: Specifies the name of the data view from which the data for the widget is taken.
• View: Returns the data view from which the data for the widget is taken.
• ParametersViewName: Specifies the name of the data view with the parameters of the inquiry.
• ParametersView: Returns the data view with the parameters of the inquiry. It can be null if the inquiry
has no parameters.
• ScreenID: Specifies the ID of the inquiry form.
• DefaultAction: Specifies the action that is performed when a user double-clicks on the row in the
details table of the inquiry form.
To access the fields of the inquiry form in the widget, use the GetFields() method of the DataScreenBase
class. This method returns the InqField class, which provides the following properties:
• Name: Specifies the internal name of the field
• DisplayName: Specifies the name of the field as it is displayed in the UI
• FieldType: Specifies the C# type of the field
• Visible: Specifies whether the field is visible in the UI
• Enabled: Specifies whether the field is enabled in the UI
• LinkCommand: Specifies the linked command of the field
To access the parameters of the inquiry form in the widget, use the GetParameters() method of the
DataScreenBase class, which returns the InqField class.
Creating Widgets for Dashboards | 13
using [Link];
using [Link];
using [Link];
using [Link];
using [Link];
using [Link];
2. Override the ProcessData() method of the widget class so that it implements all the logic for the widget
that consumes significant time while loading.
3. Override the SetProcessResult() method of the widget class so that it retrieves the result of the
processing of the widget data.
Creating Widgets for Dashboards | 14
If these methods are implemented, the system calls them automatically when it loads the widget on a dashboard
page.
You can specify how a widget should be displayed on a dashboard page by using a custom script. For example, you
can implement a script that handles the way the widget is resized.
To add a custom script to a widget, you override the RegisterScriptModules() method of the
[Link] abstract class. The following code shows an example of the
method implementation for a predefined data table widget.
using [Link];
using [Link];
The Widget Properties dialog box is displayed when a user creates or edits a widget. If you need to add
custom controls, such as buttons or grids, to this dialog box, you need to create these controls in the
RenderSettings() or RenderSettingsComplete() method of the widget class, as is described in the
sections below.
PXTextEdit te = wc as PXTextEdit;
if (te != null) switch ([Link])
{
case "ClientID":
[Link] =
"[Link]";
break;
case "ClientSecret":
[Link] =
"[Link]";
break;
case "AccessCode":
[Link] =
"[Link]";
break;
case "RedirectUri":
[Link] =
"[Link]";
break;
}
}
return true;
}
[Link](CreateSettingsPanel(ds, [Link]));
([Link] as ChartSettingsMaint).InquiryIDChanged += (s, e) =>
_btnConfig.Enabled = ;
[Link](ds, owner);
}
Customizing Business Events in Code | 17
To define the actions that the system should perform once a business event has occurred, you specify the
subscribers of this business event on the Subscribers tab of the Business Events (SM302050) form. A subscriber
of a business event is an entity that the system processes when the business event occurs. The subscriber types
available in the system include import scenarios, email notifications, mobile push notifications, and mobile SMS
notifications. You can define a custom subscriber type for business events, as described in this topic.
A custom subscriber type can be implemented in a project of your Acumatica ERP extension library.
You cannot include the custom subscriber type in a Code item of a customization project.
For more information on business events, see Business Events: General Information.
• The Name property, which is the name of the subscriber of the custom type. For the predefined
subscriber types, a user specifies the value of this property on the form that corresponds to the
subscriber. For example, for email notifications, the user specifies the value of this property in the
Description box on the Notification Templates (SM204003) form. Use the following syntax for the property.
string Name { get; }
• The Process method, which implements the actions that the system should perform once the business
event has occurred. For example, for email notifications, the method inserts values in the notification
template and sends the notification. The method uses the following syntax.
void Process(MatchedRow[] eventRows, CancellationToken cancellation);
Customizing Business Events in Code | 18
• The GetHandlers method, which retrieves the list of subscribers of the custom type. This list is
displayed in the lookup dialog box in the Subscriber ID column on the Subscribers tab of the Business
Events (SM302050) form. The method uses the following syntax.
• The RedirectToHandler method, which performs redirection to the subscriber. For example, for
email notifications, the method opens the Notification Templates form, which displays the subscriber
(which is a notification template) with the specified ID. Use the following syntax for the method.
void RedirectToHandler(Guid? handlerId);
• The Type property, which is a string identifier of the subscriber type that is exactly four characters
long. The value of this property is stored in the database. The property uses the following syntax.
string Type { get; }
• The TypeDescription property, which is a string label of the subscriber type. A user views this
value in the Type column on the Subscribers tab of the Business Events form. Use the following syntax
for the property.
string TypeDescription { get; }
If you want an action to be displayed in the Create Subscriber menu on the toolbar of
the Subscribers tab of the Business Events (SM302050) form, instead of implementing
the IBPSubscriberActionHandlerFactory interface, implement the
IBPSubscriberActionHandlerFactoryWithCreateAction interface, which also
provides methods and properties that define the creation action.
5. Compile your Acumatica ERP extension library with the implementation of the classes.
6. Open Acumatica ERP and test the new subscriber type.
Example
The following code shows an example of the implementation of a custom subscriber type. This custom subscriber
writes the body of the notification to a text file.
using System;
using [Link];
using [Link];
using [Link];
using [Link];
using [Link];
using [Link];
using [Link];
Customizing Business Events in Code | 19
using [Link];
using [Link];
using [Link];
using [Link];
using [Link];
using [Link];
using [Link];
namespace CustomSubscriber
{
//The custom subscriber that the system executes once the business event
//has occurred
public class CustomEventAction : IEventAction
{
//The GUID that identifies a subscriber
public Guid Id { get; set; }
//The method that writes the body of the notification to a text file
//once the business event has occurred
public void Process(MatchedRow[] eventRows,
CancellationToken cancellation)
{
using (StreamWriter file =
new StreamWriter(@"C:\tmp\[Link]"))
{
var graph = [Link](
_notificationTemplate.ScreenID);
var parameters = @[Link](
r => [Link]<IDictionary<string, object>,
IDictionary<string, object>>(
[Link]?.ToDictionary(c => [Link], c => [Link]),
[Link]?.ToDictionary(c => [Link],
c => ([Link] as ValueWithInternal)?.ExternalValue ??
[Link]))).ToArray();
var body = [Link](
_notificationTemplate.Body, parameters, graph, null);
[Link](body);
}
}
IBPSubscriberActionHandlerFactoryWithCreateAction
{
//The method that creates a subscriber with the specified ID
public IEventAction CreateActionHandler(Guid handlerId,
bool stopOnError, IEventDefinitionsProvider eventDefinitionsProvider)
{
var graph = [Link]<PXGraph>();
Notification notification = PXSelect<Notification,
Where<[Link],
Equal<Required<[Link]>>>>
.Select(graph, handlerId).AsEnumerable().SingleOrDefault();
//The method that retrieves the list of subscribers of the custom type
public IEnumerable<BPHandler> GetHandlers(PXGraph graph)
{
return PXSelect<Notification, Where<[Link],
Equal<Current<[Link]>>,
Or<Current<[Link]>, IsNull>>>
.Select(graph).[Link](c => c != null)
.Select(c => new BPHandler { Id = [Link], Name = [Link],
Type = [Link] });
}
[Link](graph,
[Link]);
return [Link]();
};
return [Link](handler,
new PXEventSubscriberAttribute[]
{new PXButtonAttribute {
OnClosingPopup = [Link]}});
}
}
//Localizable messages
[PXLocalizable]
public static class LocalizableMessages
{
public const string CustomNotification = "Custom Notification";
public const string CreateCustomNotification = "Custom Notification";
}
}
• [Link]
• [Link]
• [Link]
• [Link]
3. Create a business event on the Business Events (SM302050) form. For details on the creation of a business
event, see To Monitor Changes of the Business Process Data and To Monitor the Business Process Data by a
Schedule.
4. On the Subscribers tab, make sure Custom Notification is available in the Type column.
5. Select Custom Notification in the Type column. Make sure the list in the Subscriber ID column is empty.
6. On the table toolbar, make sure Create Subscriber > Custom Notification is available.
7. Click Create Subscriber > Custom Notification. Make sure the Notification Templates (SM204003) form
opens.
8. On the Notification Templates form, create a notification template with a message that is not empty on the
Messages tab. For details about notification templates, see Notification Templates.
9. Make sure the created notification template is added to the Subscribers tab of the Business Events form as
the Custom Notification subscriber and save the business event.
[Link] changes in the system to trigger the business event that you have configured.
[Link] sure the text file (in this example, C:\tmp\[Link]) contains the body of the notification.
Related Links
• Business Events: General Information
Implementing Plug-Ins for Processing Credit Card Payments | 23
Acumatica ERP provides the interfaces for the implementation of plug-ins for credit card payment processing.
By using these interfaces, you can implement tokenized processing plug-ins. When a tokenized processing plug-
in is used, the credit card information is not saved to the application database; this information is stored only at
the processing center. The Acumatica ERP database stores only the identification tokens that link customers and
payment methods in the application with the credit card data at the processing center.
For details on how to implement custom plug-ins, see To Implement a Plug-In for Processing Credit Card Payments.
Mandatory Interfaces
The root interface for implementation of custom plug-ins for credit card processing is
[Link]. The system automatically discovers
the class that implements the ICCProcessingPlugin interface in the Bin folder of the Acumatica ERP instance
and includes it in the list in the Payment Plug-In (Type) box on the Processing Centers (CA205000) form. For
creation of a custom plug-in, you also need to implement the ICCTransactionProcessor interface to process
credit card transactions.
Additional Interfaces
You can implement the following additional functionality:
• Tokenized credit card processing: Implement the ICCProfileProcessor and
ICCHostedFormProcessor interfaces.
• Processing of payments from new credit cards: To use this functionality, implement
the ICCProfileCreator, ICCHostedPaymentFormProcessor,
ICCHostedPaymentFormResponseParser, and ICCTransactionGetter interfaces. If this
functionality is implemented in a custom plug-in, a user can select the Accept Payment from New Card
check box on the Processing Centers form for the processing centers that use this custom plug-in.
• Synchronization of credit cards with the processing center: Implement the ICCTransactionGetter
interface in a custom plug-in. If this functionality is implemented, on the Synchronize Cards (CA206000)
form, users can work with the processing centers that use this custom plug-in.
Implementing Plug-Ins for Processing Credit Card Payments | 24
• Retrieval of the information about suspicious credit card transactions (without the use of the hosted form
that accepts payments): To use this functionality, implement the ICCTranStatusGetter interface.
• Webhooks as a way to obtain a response from the processing center: Implement the
ICCWebhookProcessor and ICCWebhookResolver interfaces.
Related Links
• To Implement a Plug-In for Processing Credit Card Payments
In Acumatica ERP, the [Link] API built-in plug-in processes transactions in [Link]. For more
information on this plug-in, see Integration with [Link] Through the API Plug-in. You can implement your own
plug-in for working with processing centers other than [Link], as described in this topic.
using [Link].V2;
• In the ValidateSettings method, validate the settings modified on the Processing Centers form,
which are passed as the parameter of the method, and return null if validation was successful or an
error message if validation failed. The syntax of the method is shown in the following code.
string ValidateSettings(SettingsValue setting);
• In the TestCredentials method, check the connection to the payment gateway by using the
credentials that are specified by the user on the Processing Centers form. The syntax of the method is
shown in the following code.
Implementing Plug-Ins for Processing Credit Card Payments | 26
• In the CreateProcessor<T> method, return a new object of the T type, and initialize the object with
the settings passed to the method. The T type can be any of the following:
• ICCTransactionProcessor
• ICCProfileProcessor
• ICCHostedFormProcessor
• ICCProfileCreator
• ICCHostedPaymentFormProcessor
• ICCTransactionGetter
• ICCTranStatusGetter
• ICCWebhookResolver
• ICCWebhookProcessor
If a particular T type is not supported by your plug-in, return null for this type. The following code
shows a sample implementation of the method.
public T CreateProcessor<T>(IEnumerable<SettingsValue> settingValues)
where T : class
{
if (typeof(T) == typeof(ICCProfileProcessor))
{
return new MyProfileProcessor(settingValues) as T;
}
if (typeof(T) == typeof(ICCHostedFormProcessor))
{
return new MyHostedFormProcessor(settingValues) as T;
}
if (typeof(T) == typeof(ICCHostedPaymentFormProcessor))
{
return new MyHostedPaymentFormProcessor(settingValues) as T;
}
if (typeof(T) == typeof(ICCHostedPaymentFormResponseParser))
{
return new MyHostedPaymentFormResponseParser(settingValues) as T;
}
if (typeof(T) == typeof(ICCTransactionProcessor))
{
return new MyTransactionProcessor(settingValues) as T;
}
if (typeof(T) == typeof(ICCTransactionGetter))
{
return new MyTransactionGetter(settingValues) as T;
}
if (typeof(T) == typeof(ICCProfileCreator))
{
return new MyProfileCreator(settingValues) as T;
}
if (typeof(T) == typeof(ICCWebhookResolver))
{
return new MyWebhookResolver() as T;
}
if (typeof(T) == typeof(ICCWebhookProcessor))
{
return new MyWebhookProcessor(settingValues) as T;
Implementing Plug-Ins for Processing Credit Card Payments | 27
}
if (typeof(T) == typeof(ICCTranStatusGetter))
{
return new MyTranStatusGetter() as T;
}
return null;
}
Related Links
• Interfaces for Processing Credit Card Payments
• [Link].V2 Namespace
Implementing a Connector for an E-Commerce System | 28
A connector between Acumatica ERP and an external e-commerce system is a plug-in that synchronizes particular
entities in Acumatica ERP with the corresponding entities in the external system. This synchronization can be
performed based on a schedule or by a request received with a push notification.
Classes for Acumatica ERP entities are adapters for the entities of the contract-based REST API of
Acumatica ERP. To implement these classes, you have two options: Define the classes on your own, or
use the classes defined for the default BigCommerce and Shopify integrations, which are available in the
[Link] namespace. For example, if you need to work with customers in Acumatica ERP, you
can use the predefined [Link] class. If the entity is not implemented in the
[Link] namespace, you implement a custom class for this entity.
If the API defined in the eCommerce/20.200.001 endpoint is not sufficient for the implementation of
your connector, you can create a custom endpoint and use it for your connector. For details about
the creation of a custom endpoint, see To Create a Custom Endpoint in the Integration Development
Guide. To use the custom endpoint for your connector, you need to replace the PXDefault attribute
of the [Link] and [Link] fields by
using the corresponding CacheAttached event handlers in the graph of the configuration form. For
details about the replacement, see Replacement of Attributes for DAC Fields in CacheAttached.
Implementing a Connector for an E-Commerce System | 30
You assign the [Link] attributes with the names of the entity and its
properties to the class and its properties, respectively. These names are used as the names of the Acumatica ERP
objects and fields on the mapping and filtering tabs of the Entities (BC202000) form.
Example
An example of the implementation of a class derived from the CBAPIEntity is shown in the following code.
using [Link];
using [Link];
using [Link];
[CommerceDescription("Case")]
public partial class Case : CBAPIEntity
{
public GuidValue NoteID { get; set; }
[CommerceDescription("CaseCD")]
public StringValue CaseCD { get; set; }
[CommerceDescription("Subject")]
public StringValue Subject { get; set; }
[CommerceDescription("Description")]
public StringValue Description { get; set; }
}
Related Links
• Architecture of a Commerce Connector
• To Create a Connector for an External System
Classes for external entities are adapters for the entities of the REST API of the external e-commerce system.
• Optional: A FieldFilterStatus value, which specifies whether the field is used on the filtering tabs of
the Entities form.
• Optional: A FieldMappingStatus value, which specifies whether the field is used on the mapping tabs
of the Entities form.
• Optional: A String value, which specifies the feature on which the field depends, such as
"[Link]+userDefinedOrderTypes". The field is available on the Entities
form if the specified feature is enabled on the Enable/Disable Features (CS100000) form.
You also assign the [Link] attribute to the properties of the class to map them to
the properties of the external entity retrieved in JSON format through the external REST API.
The classes and the communication with the external system depend on the external system.
Example
The following code shows an example of the declaration of a customer entity for the WooCommerce connector.
using System;
using [Link];
using [Link];
using [Link];
namespace WooCommerceTest
{
[CommerceDescription([Link])]
public class CustomerData : BCAPIEntity, IWooEntity
{
[JsonProperty("id")]
[CommerceDescription([Link], [Link],
[Link])]
public int? Id { get; set; }
[JsonProperty("date_created")]
public DateTime? DateCreatedUT { get; set; }
[CommerceDescription([Link])]
[ShouldNotSerialize]
public virtual DateTime? CreatedDateTime
{
get
{
return DateCreatedUT != null ? (DateTime)[Link]() : default;
}
}
[JsonProperty("date_modified_gmt")]
public DateTime? DateModified { get; set; }
[CommerceDescription([Link])]
[ShouldNotSerialize]
public virtual DateTime? ModifiedDateTime
{
get
{
return DateModified != null ? (DateTime)[Link]() : default;
}
Implementing a Connector for an E-Commerce System | 32
[JsonProperty("email")]
[CommerceDescription([Link], [Link],
[Link])]
[ValidateRequired]
public string Email { get; set; }
[JsonProperty("first_name")]
[CommerceDescription([Link], [Link],
[Link])]
[ValidateRequired]
public string FirstName { get; set; }
[JsonProperty("last_name")]
[CommerceDescription([Link], [Link],
[Link])]
[ValidateRequired()]
public string LastName { get; set; }
[JsonProperty("username")]
[CommerceDescription([Link], [Link],
[Link])]
public string Username { get; set; }
[JsonProperty("billing")]
public CustomerAddressData Billing { get; set; }
[JsonProperty("shipping")]
public CustomerAddressData Shipping { get; set; }
}
}
}
Related Links
• Architecture of a Commerce Connector
• To Create a Connector for an External System
Mapping Classes
For each pair of an internal entity and an external entity that should be synchronized, you implement a mapping
class, which defines the correspondence between the properties of the internal and external entities.
Example
An example of the implementation of a mapping class is shown in the following code.
using System;
using [Link];
using [Link];
namespace WooCommerceTest
{
public class MappedCustomer : MappedEntity<CustomerData, Customer>
{
public const string TYPE = [Link];
public MappedCustomer()
: base([Link], TYPE)
{ }
public MappedCustomer(Customer entity, Guid? id, DateTime? timestamp)
: base([Link], TYPE, entity, id, timestamp) { }
public MappedCustomer(CustomerData entity, string id, DateTime? timestamp)
: base([Link], TYPE, entity, id, timestamp) { }
}
}
Related Links
• Architecture of a Commerce Connector
• To Create a Connector for an External System
Bucket Classes
For the synchronization of data between an external system and Acumatica ERP, for each entity that you need to
synchronize, you need to define a bucket class that defines the entities to be synchronized before and aer the
synchronization of this entity.
In the bucket class, you need to define the entity that is being synchronized in the Primary property of the bucket
class. In the Entities property of the bucket class, you specify all entities that are being synchronized in this
bucket. The PreProcessors property contains the list of entities that should be synchronized before the entity
is, while the PostProcessors property specifies the list of entities that should be synchronized aer the entity
is.
Example
Suppose that aer the synchronization of address entities between an external system and Acumatica ERP, you
need to synchronize customer entities. The following code shows an example of the bucket class implementation
for the address and customer entities.
Related Links
• Architecture of a Commerce Connector
• To Create a Connector for an External System
Processor Classes
For each pair of entities that you want to synchronize between an e-commerce system and Acumatica ERP, you
need to create a processor class.
A processor classes perform the following functions:
• Retrieval of the Acumatica ERP records and external records
• Providing of the default mapping logic
• Providing of the export and import logic
Both of these classes are graphs. Therefore, the processor class is a graph as well. In the processor base class, you
pass as the type parameters of the class the graph itself, the bucket class, and the mapping class for the pair of
entities that you want to synchronize.
Example
The following code shows an example of the processor implementation.
using [Link];
using [Link];
using [Link];
using [Link];
using [Link];
using System;
using [Link];
using [Link];
namespace WooCommerceTest
{
[BCProcessor(typeof(WooCommerceConnector), [Link],
[Link],
IsInternal = false,
Direction = [Link],
PrimaryDirection = [Link],
PrimarySystem = [Link],
PrimaryGraph = typeof([Link]),
ExternTypes = new Type[] { typeof(CustomerData) },
LocalTypes = new Type[] { typeof([Link]) },
AcumaticaPrimaryType = typeof([Link]),
AcumaticaPrimarySelect = typeof([Link]),
URL = "[Link]?user_id={0}"
)]
[BCProcessorRealtime(PushSupported = false, HookSupported = false)]
public class WooCustomerProcessor : BCProcessorSingleBase<WooCustomerProcessor,
WooCustomerEntityBucket, MappedCustomer>, IProcessor
{
public WooRestClient client;
Implementing a Connector for an E-Commerce System | 36
#region Constructor
public override void Initialise(IConnector iconnector,
ConnectorOperation operation)
{
[Link](iconnector, operation);
client = [Link](
GetBindingExt<BCBindingWooCommerce>());
customerDataProvider = new CustomerDataProvider(client);
}
#endregion
#region Import
public override void MapBucketImport(WooCustomerEntityBucket bucket,
IMappedEntity existing)
{
MappedCustomer customerObj = [Link];
[Link] = GetBillingCustomerName([Link]).
ValueField();
[Link] = [Link]([Link],
GetBinding().BindingName).ValueField();
[Link] = [Link] == null ||
existing?.Local == null ?
GetBindingExt<BCBindingExt>().CustomerClassID?.ValueField() : null;
//Primary Contact
[Link] = GetPrimaryContact([Link]);
[Link] = SetBillingContact([Link]);
[Link] = [Link]();
[Link] = [Link]();
[Link] = SetShippingContact([Link]);
}
}
Implementing a Connector for an E-Commerce System | 37
return contact;
}
return contactImpl;
}
return contactImpl;
}
? [Link]
: [Link]($"{[Link]?.FirstName} {
[Link]?.LastName}") ? [Link] : $"{
[Link]?.FirstName} {[Link]?.LastName}";
}
return address;
}
return result;
Implementing a Connector for an E-Commerce System | 39
#region Export
#region Pull
public override MappedCustomer PullEntity(Guid? localID,
Dictionary<string, object> externalInfo)
{
throw new NotImplementedException();
}
public override MappedCustomer PullEntity(string externID,
string externalInfo)
{
throw new NotImplementedException();
}
return status;
}
return status;
}
#endregion
}
public static class PhoneTypes
{
public static string Business1 = "B1";
}
}
Related Links
• Architecture of a Commerce Connector
• To Create a Connector for an External System
Implementing a Connector for an E-Commerce System | 41
Connector Class
The connector class is the main class of a connector for an e-commerce system.
The connector class performs the following functions:
• Provides the settings for connection with the external system
• Implements navigation to external records
• Performs the synchronization of records of the external system and Acumatica ERP
• Implements real-time subscription and processing
Example
The following code shows an example of the connector class implementation.
using [Link];
using System;
using [Link];
using [Link];
using [Link];
using CommonServiceLocator;
using [Link];
using [Link];
using [Link];
using [Link];
using [Link];
namespace WooCommerceTest
{
public class WooCommerceConnector: BCConnectorBase<WooCommerceConnector>,
IConnector
{
public const string TYPE = "WOO";
public const string NAME = "WooCommerce";
try
{
List<TInfo> result = new List<TInfo>();
return result;
}
catch (Exception ex)
{
LogError(new BCLogTypeScope(typeof(WooCommerceConnector)), ex);
}
return null;
}
if ([Link](bCBindingBigCommerce?.StoreAdminUrl) ||
[Link]([Link])) return;
[Link] == [Link]);
using (IProcessor graph = (IProcessor)CreateInstance([Link]))
{
[Link](this, operation);
return [Link](syncIDs);
}
}
return dtLocal;
}
DateTimeZoneHandling = [Link],
ContractResolver = new GetOnlyContractResolver()
};
RestJsonSerializer restSerializer = new RestJsonSerializer(serializer);
WooRestClient client = new WooRestClient(restSerializer, restSerializer,
options,
[Link]<[Link]>());
return client;
}
}
}
Related Links
• Architecture of a Commerce Connector
• To Create a Connector for an External System
A processor factory class creates processor classes for all entities used by the specified connector.
Base Interface
The processor factory class implements the [Link] base interface. You
implement one factory class for all processor classes.
In the processor factory class, you specify the type of the connector in the ConnectorType property. This is the
type that is defined in the ConnectorType property of the connector class.
You also implement the GetProcessorTypes() method, which returns the list of processors of the connector.
This method defines the order in which the entities are processed if Process All is clicked on the form toolbar of the
Process Data (BC501500) form.
Example
An example of the implementation of a processor factory class is shown in the following code.
using System;
using [Link];
using [Link];
namespace WooCommerceTest
{
public class WooCommerceProcessorsFactory : IProcessorsFactory
{
public string ConnectorType => [Link];
Related Links
• Architecture of a Commerce Connector
• To Create a Connector for an External System
A connector descriptor class is a supplementary class that is used by the connector class. A connector descriptor
class provides performance-optimized access to particular functions of the connector. A connector descriptor
generates unique IDs for the real-time synchronization, returns the list of supported entities, and provides access to
the dynamic fields of the external system for the mapping.
Base Interface
A connector descriptor class implements the [Link] base interface.
Example
The following code shows an example of the implementation of the connector descriptor class.
using System;
using [Link];
using [Link];
namespace WooCommerceTest
{
public class WooCommercesConnectorDescriptor : IConnectorDescriptor
{
protected IList<EntityInfo> _entities;
return fieldsList;
}
}
Implementing a Connector for an E-Commerce System | 46
Related Links
• Architecture of a Commerce Connector
• To Create a Connector for an External System
A connector factory class initializes a connector that has the specified type and name.
Example
An example of the implementation of a connector factory class is shown in the following code.
using [Link];
using [Link];
using [Link];
namespace WooCommerceTest
{
public class WooCommerceConnectorFactory :
BaseConnectorFactory<WooCommerceConnector>, IConnectorFactory
{
public override string Description => [Link];
public override bool Enabled => true;
Related Links
• Architecture of a Commerce Connector
• To Create a Connector for an External System
In this chapter, you will learn how a custom commerce connector—that is, a custom connector for any e-commerce
system—is used in Acumatica ERP.
The Connector box of the configuration form of an e-commerce store contains the name of the connector if both of
the following conditions are met:
• The library that contains the connector class is placed in the Bin folder of an instance of Acumatica ERP or
an Acumatica Framework-based application.
• The ASPX file of the configuration form of the connector is available in the Pages folder of the application.
The description in this topic assumes that the connector uses a standard configuration form, which is
similar to the BigCommerce Stores (BC201000) and Shopify Stores (BC201010) forms. As an example,
this topic uses the WooCommerce Stores form, whose implementation is described in Step 13:
Creating the Configuration Form of this guide.
The data of the Summary area of the configuration form is obtained from the Bindings predefined data view of
the BCStoreMaint graph, which is a base class of the graph through which the configuration form is managed
(which is the WooCommerceStoreMaint graph in the WooCommerce example). The Bindings data view
retrieves data from the BCBinding predefined database table. This table includes the ConnectorType column,
which stores the type of the connector.
The system obtains the type and name of a connector from the implementation of the Type and Description
properties of the IConnectorFactory interface. The ConnectorType field of the BCBinding DAC, which
corresponds to the BCBinding table, has the BCConnectors attribute assigned to it. The BCConnectors
attribute, which derives from the PXStringList attribute, matches the type of the connector to its name.
The custom CacheAttached<[Link]> event handler sets the default value of the
ConnectorType field to the type of the connector. The Connector box displays the connector name, which
corresponds to this default value.
When a user specifies the settings of a commerce store in the Summary area of the configuration form, the system
saves these settings in the BCBinding database table.
The following diagram illustrates the relations of the classes of the connector and the ASPX page.
Implementing a Connector for an E-Commerce System | 48
When a user specifies the connection settings of a store on the Configuration Settings tab of the configuration
form, the system saves the settings in the custom table (the BCBindingWooCommerce database table in the
WooCommerce example).
When the user clicks the Test Connection button on the form toolbar of the configuration form, the
system retrieves the data for the connection with the store from this custom table. The system calls the
GetRestClient() static method of the implementation of the IConnector interface, which creates the REST
client of the external system. The REST client establishes the connection with the external system through the REST
API.
The classes and the communication with the external system depend on the external system.
Implementing a Connector for an E-Commerce System | 49
The preparation for data synchronization can be started manually on the Prepare Data (BC501000) form, by an
automation schedule, or as a result of a push notification. The following sections describe in order the general
steps involved in the preparation of data for synchronization; the process diagram in the end of this topic shows the
data preparation process in its entirety.
This topic describes the preparation for data synchronization when it is initiated on the Prepare
Data form. The preparation process is slightly different if it is initiated by an automation schedule
or as a result of a push notification. The preparation process initiated by an automation schedule
starts with Fetching of the Data from Acumatica ERP and the External System. The preparation
process initiated as a result of a push notification calls the [Link]()
method instead of the [Link]() and
[Link]() methods.
Fetching of the Data from Acumatica ERP and the External System
The processor calls the FetchBucketsImport() (Item 4a) or FetchBucketsExport() (Item
4b) method, depending on the sync direction of the entity. These methods retrieve the information
about the synchronization of the entity from the BCEntityStats database table (Items 5a and
5b), determine the time frame for the entities that should be prepared (depending on the prepare
mode, which for manual preparation is specified in the Prepare Mode box on the Prepare Data
form), and call the BCProcessorSingleBase<>.FetchBucketsForImport() (Item 6a) or
BCProcessorSingleBase<>.FetchBucketsForExport() (Item 6b) method, respectively. The
FetchBucketsForImport() method retrieves entity records from the external system (Item 7a). The
FetchBucketsForExport() method obtains entity records from Acumatica ERP (Item 7b).
Process Diagram
The following diagram illustrates the preparation process.
The synchronization of the prepared data can be started manually on the Process Data (BC501500) form, by an
automation schedule, or as a result of the push notification. The following sections describe in order the general
steps involved in the synchronization of the prepared data; the process diagram in the end of this topic shows the
synchronization process in its entirety.
2. If the value in LocalID of the BCEntityStats record is empty, searches for an internal record that
corresponds to the external record in BCProcessorSingleBase<>.CheckExistingForImport()
(Item 8b).
Forced synchronization is performed if a user clicks Sync on the toolbar of the Sync History
(BC301000) form.
3. If only one time stamp is changed, the processor chooses the direction to synchronize changes from the
changed record.
Import of Records
The processor imports the data to Acumatica ERP in the BCProcessorSingleBase<>.ProcessImport()
method (Item 14a), which internally performs the following steps:
1. For the records that have been deleted, removes the related records in Acumatica ERP in the
BCProcessorBase<>.DeleteRelatedEntities() method.
Implementing a Connector for an E-Commerce System | 53
2. Maps the properties of the external object to the properties of the Acumatica ERP object in
BCProcessorBase<>.MapBucketImport() method.
3. Applies the mappings that have been defined by the user on the Entities (BC202000) form in the
BCProcessorBase<>.RemapBucketImport() method.
4. In the BCProcessorBase<>.ValidateImport() method, validates the entity that is going to be
saved to the Acumatica ERP database to ensure that no required fields are le empty.
5. Saves the data to the database in BCProcessorSingleBase<>.SaveBucketImport() (Item 15a).
In the end of the implementation of the SaveBucketImport() method, you need to call
the BCProcessorBase<>.UpdateStatus() method to update the time stamps and IDs
of the synchronized records. For details, see Update of the Synchronization Status.
Export of Records
The processor exports the data from Acumatica ERP to the external system in the
BCProcessorSingleBase<>.ProcessExport() method (Item 14b), which internally performs the
following steps:
1. For the records that have been deleted, removes the related records in the external system in the
BCProcessorBase<>.DeleteRelatedEntities() method.
2. Maps the properties of the Acumatica ERP object to the properties of the external object in
BCProcessorBase<>.MapBucketExport() method.
3. Applies the mappings that have been defined by the user on the Entities (BC202000) form in the
BCProcessorBase<>.RemapBucketExport() method.
4. In the BCProcessorBase<>.ValidateExport() method, validates the record that is going to be
saved to the external system to ensure that no required fields are le empty.
5. Saves the data to the external system in BCProcessorSingleBase<>.SaveBucketExport() (Item
15b).
In the end of the implementation of the SaveBucketExport() method, you need to call
the BCProcessorBase<>.UpdateStatus() method to update the time stamps and IDs
of the synchronized records. For details, see Update of the Synchronization Status.
Process Diagram
The following diagram illustrates the process of data synchronization.
Implementing a Connector for an E-Commerce System | 55
With real-time synchronization, Acumatica ERP attempts to prepare data or prepare and process data as soon
as a change occurs in Acumatica ERP or in the e-commerce system. Depending on the entity involved and your
company's processes, real-time synchronization can involve import or export or can be bidirectional. Real-time
import relies on webhooks that Acumatica ERP receives from an e-commerce system, and real-time export makes
use of the push notification mechanism available in Acumatica ERP.
For details about push notifications in Acumatica ERP, see Configuring Push Notifications. For information about
how to implement push notifications for a custom connector, see To Implement Push Notifications for a Commerce
Connector.
You can create custom generic inquiries for the monitoring of entities that are not included in the predefined
inquiries. You can then include these custom generic inquiries in the list of inquiries for the Commerce notification
destination. Also, you can create a custom notification destination of the Commerce Push Destination type on the
Push Notifications form and include both the custom generic inquires and the needed predefined generic inquiries
in the list of inquiries for this notification destination.
• If Prepare is selected, places the record in the BCSyncStatus table with the Pending status. For details
about preparation of data for synchronization, see Preparation of Data for the Synchronization.
• If Prepare & Process is selected, aer placing the record in the BCSyncStatus table, immediately starts the
synchronization of the record. For more information about data synchronization, see Synchronization of the
Prepared Data.
The processing of notifications is implemented in the [Link]() method, whose
default implementation is available in the [Link]<TConnector>.
This default implementation calls the [Link]() method, which is implemented in the
[Link]<>.ProcessPush() method.
If any error appears during the preparation or processing of the data for synchronization, the error is displayed on
the System Events tab of the System Monitor (SM201530) form.
To create a commerce connector, you need to perform the basic steps that are described in this chapter.
The chapter presents an example of the integration of Acumatica ERP with WooCommerce in which you need to
synchronize customers in Acumatica ERP with customers in a WooCommerce store.
You can find the code and customization project of the example described in this chapter in the Help-and-Training-
Examples repository on GitHub. The full code and customization project of the WooCommerce connector is
available in the Acumatica-WooCommerce repository on GitHub.
You need to develop a commerce connector in an extension library, which you include in an Acumatica ERP
customization package.
For details about the creation of an extension library, see To Create an Extension Library.
Acumatica Customization Platform adds the default references to the project of the extension library, such as
[Link] and [Link]. For the creation of a commerce connector, you also need to add the references to the
Acumatica ERP commerce libraries, as described below.
• [Link]: Necessary only if you implement custom classes for Acumatica ERP
entities, as described in Step 2: Creating Classes for Acumatica ERP Entities
• [Link]: Necessary only if you use fluent BQL queries instead of traditional BQL
queries
2. Add references to the following external libraries:
You need to use the same version of the library as the one located in the Bin folder.
• RestSharp
• CommonServiceLocator
• [Link]
• [Link]
• Serilog
3. Build the project.
Now you will include the dll file of the extension library, which is located in the Bin folder of the website, in the
customization project.
• You can select multiple custom files to add them to the project at the same time.
• For any files other than the ones placed in the Bin folder, you can click Refresh on the
toolbar of the Add Files dialog box to make the system update the list of files in the table. If
you have changed files in the Bin folder of the website, you should refresh the page in the
browser.
5. In the dialog box, click Save to save each selected file to the customization project as a File item.
You need to create classes for the Acumatica ERP entities to be synchronized with the external system through the
connector. For details about the classes, see Classes for Acumatica ERP Entities.
If the default classes are enough for your implementation, skip the instructions in the
following section.
For the WooCommerce connector in this example, you will use the default Customer entity.
[CommerceDescription("Case")]
public partial class Case : CBAPIEntity
{
public GuidValue NoteID { get; set; }
[CommerceDescription("CaseCD")]
public StringValue CaseCD { get; set; }
[CommerceDescription("Subject")]
public StringValue Subject { get; set; }
[CommerceDescription("Description")]
public StringValue Description { get; set; }
}
2. Repeat the previous instruction for each Acumatica ERP entity that you need to use in your connector.
3. Build the project.
Now you will create classes for the external entities that you need to synchronize with the Acumatica ERP system
through the connector. For details about the classes, see Classes for External Entities.
The classes and the communication with the external system depend on the external system.
Implementing a Connector for an E-Commerce System | 59
namespace WooCommerceTest
{
[CommerceDescription([Link])]
public class CustomerData : BCAPIEntity, IWooEntity
{
[JsonProperty("id")]
[CommerceDescription([Link], [Link],
[Link])]
public int? Id { get; set; }
[JsonProperty("date_created")]
public DateTime? DateCreatedUT { get; set; }
[CommerceDescription([Link])]
[ShouldNotSerialize]
public virtual DateTime? CreatedDateTime
{
get
{
return DateCreatedUT != null ? (DateTime)[Link]() :
default;
}
}
[JsonProperty("date_modified_gmt")]
public DateTime? DateModified { get; set; }
[CommerceDescription([Link])]
[ShouldNotSerialize]
public virtual DateTime? ModifiedDateTime
{
get
{
return DateModified != null ? (DateTime)[Link]() :
default;
}
}
Implementing a Connector for an E-Commerce System | 60
[JsonProperty("email")]
[CommerceDescription([Link], [Link],
[Link])]
[ValidateRequired]
public string Email { get; set; }
[JsonProperty("first_name")]
[CommerceDescription([Link], [Link],
[Link])]
[ValidateRequired]
public string FirstName { get; set; }
[JsonProperty("last_name")]
[CommerceDescription([Link], [Link],
[Link])]
[ValidateRequired()]
public string LastName { get; set; }
[JsonProperty("username")]
[CommerceDescription([Link], [Link],
[Link])]
public string Username { get; set; }
[JsonProperty("billing")]
public CustomerAddressData Billing { get; set; }
[JsonProperty("shipping")]
public CustomerAddressData Shipping { get; set; }
}
}
}
2. Repeat the previous instruction for each external entity that you need to use in your connector. For the
WooCommerce connector, you also need the following classes:
• CustomerAddressData, shown in the code below
using System;
using [Link];
using [Link];
using [Link];
namespace WooCommerceTest
{
public class CustomerAddressData : BCAPIEntity,
IEquatable<CustomerAddressData>
{
[JsonProperty("customer_id")]
[CommerceDescription([Link],
[Link], [Link])]
Implementing a Connector for an E-Commerce System | 61
[JsonProperty("first_name")]
[CommerceDescription([Link], [Link],
[Link])]
[ValidateRequired()]
public virtual string FirstName { get; set; }
[JsonProperty("last_name")]
[CommerceDescription([Link], [Link],
[Link])]
public virtual string LastName { get; set; }
[JsonProperty("company")]
[CommerceDescription([Link], [Link],
[Link])]
public virtual string Company { get; set; }
[JsonProperty("address_1")]
[CommerceDescription(WCCaptions.AddressLine1, [Link],
[Link])]
[ValidateRequired(AutoDefault = true)]
public string Address1 { get; set; }
[JsonProperty("address_2")]
[CommerceDescription(WCCaptions.AddressLine2, [Link],
[Link])]
public string Address2 { get; set; }
[JsonProperty("city")]
[CommerceDescription([Link], [Link],
[Link])]
[ValidateRequired(AutoDefault = true)]
public virtual string City { get; set; }
[JsonProperty("postcode")]
[CommerceDescription([Link], [Link],
[Link])]
[ValidateRequired(AutoDefault = true)]
public virtual string PostalCode { get; set; }
[JsonProperty("country")]
[CommerceDescription([Link], [Link],
[Link])]
[ValidateRequired()]
public virtual string Country { get; set; }
[JsonProperty("state")]
[Description([Link])]
[ValidateRequired(AutoDefault = true)]
public virtual string State { get; set; }
[JsonProperty("phone")]
[Description([Link])]
[ValidateRequired(AutoDefault = true)]
public virtual string Phone { get; set; }
Implementing a Connector for an E-Commerce System | 62
[JsonProperty("email")]
[Description([Link])]
[ValidateRequired(AutoDefault = true)]
public virtual string Email { get; set; }
return result;
}
}
}
• SystemStatusData and related entities, which are shown in the following code
using [Link];
namespace WooCommerceTest
{
public class SystemStatusData
{
[JsonProperty("environment")]
public Environment Environment { get; set; }
[JsonProperty("settings")]
public Settings Settings { get; set; }
}
[JsonProperty("force_ssl")]
public bool? ForceSsl { get; set; }
[JsonProperty("currency")]
public string Currency { get; set; }
Implementing a Connector for an E-Commerce System | 63
[JsonProperty("currency_symbol")]
public string CurrencySymbol { get; set; }
[JsonProperty("currency_position")]
public string CurrencyPosition { get; set; }
[JsonProperty("thousand_separator")]
public string ThousandSeparator { get; set; }
[JsonProperty("decimal_separator")]
public string DecimalSeparator { get; set; }
[JsonProperty("number_of_decimals")]
public int? NumberOfDecimals { get; set; }
[JsonProperty("geolocation_enabled")]
public bool? GeolocationEnabled { get; set; }
[JsonProperty("woocommerce_com_connected")]
public string WoocommerceComConnected { get; set; }
}
[JsonProperty("site_url")]
public string SiteUrl { get; set; }
[JsonProperty("version")]
public string Version { get; set; }
[JsonProperty("log_directory")]
public string LogDirectory { get; set; }
[JsonProperty("log_directory_writable")]
public bool? LogDirectoryWritable { get; set; }
[JsonProperty("wp_version")]
public string WpVersion { get; set; }
[JsonProperty("wp_multisite")]
public bool? WpMultisite { get; set; }
[JsonProperty("wp_memory_limit")]
public int? WpMemoryLimit { get; set; }
[JsonProperty("wp_debug_mode")]
public bool? WpDebugMode { get; set; }
[JsonProperty("wp_cron")]
public bool? WpCron { get; set; }
[JsonProperty("language")]
public string Language { get; set; }
Implementing a Connector for an E-Commerce System | 64
[JsonProperty("external_object_cache")]
public object ExternalObjectCache { get; set; }
[JsonProperty("server_info")]
public string ServerInfo { get; set; }
[JsonProperty("php_version")]
public string PhpVersion { get; set; }
[JsonProperty("php_post_max_size")]
public int? PhpPostMaxSize { get; set; }
[JsonProperty("php_max_execution_time")]
public int? PhpMaxExecutionTime { get; set; }
[JsonProperty("php_max_input_vars")]
public int? PhpMaxInputVars { get; set; }
[JsonProperty("curl_version")]
public string CurlVersion { get; set; }
[JsonProperty("suhosin_installed")]
public bool? SuhosinInstalled { get; set; }
[JsonProperty("max_upload_size")]
public int? MaxUploadSize { get; set; }
[JsonProperty("mysql_version")]
public string MysqlVersion { get; set; }
[JsonProperty("mysql_version_string")]
public string MysqlVersionString { get; set; }
[JsonProperty("default_timezone")]
public string DefaultTimezone { get; set; }
[JsonProperty("fsockopen_or_curl_enabled")]
public bool? FsockopenOrCurlEnabled { get; set; }
[JsonProperty("soapclient_enabled")]
public bool? SoapclientEnabled { get; set; }
[JsonProperty("domdocument_enabled")]
public bool? DomdocumentEnabled { get; set; }
[JsonProperty("gzip_enabled")]
public bool? GzipEnabled { get; set; }
[JsonProperty("mbstring_enabled")]
public bool? MbstringEnabled { get; set; }
[JsonProperty("remote_post_successful")]
public bool? RemotePostSuccessful { get; set; }
[JsonProperty("remote_post_response")]
public int RemotePostResponse { get; set; }
Implementing a Connector for an E-Commerce System | 65
[JsonProperty("remote_get_successful")]
public bool? RemoteGetSuccessful { get; set; }
[JsonProperty("remote_get_response")]
public int? RemoteGetResponse { get; set; }
}
}
3. Add the constants that you use for the descriptions of the entities and their fields to a class with the
PXLocalizable attribute, as shown in the following code.
using [Link];
namespace WooCommerceTest
{
[PXLocalizable]
public static class WCCaptions
{
public const string AddressLine1 = "Address Line 1";
public const string AddressLine2 = "Address Line 2";
public const string City = "City";
public const string CustomerId = "Customer ID";
public const string FirstName = "First Name";
public const string LastName = "Last Name";
public const string CompanyName = "Company Name";
public const string Country = "Country";
public const string PostalCode = "Postal Code";
public const string State = "State";
public const string Phone = "Phone";
public const string Email = "Email";
public const string Customer = "Customer";
public const string DateCreatedUT = "Date Created UT";
public const string DateModifiedUT = "Date Modified UT";
public const string ID = "ID";
public const string UserName = "UserName";
}
}
For each pair of an internal entity and an external entity that should be synchronized, you must create a mapping
class. For details about the mapping class, see Mapping Classes.
namespace WooCommerceTest
{
public class WooCommerceConnector
{
Implementing a Connector for an E-Commerce System | 66
2. For each pair of an internal entity and an external entity that should be synchronized, in
the Visual Studio project of the extension library, create a mapping class that derives from
[Link]<ExternType, LocalType>. The following example shows
the implementation of the MappedCustomer class.
using System;
using [Link];
using [Link];
namespace WooCommerceTest
{
public class MappedCustomer : MappedEntity<CustomerData, Customer>
{
public const string TYPE = [Link];
public MappedCustomer()
: base([Link], TYPE)
{ }
public MappedCustomer(Customer entity, Guid? id, DateTime? timestamp)
: base([Link], TYPE, entity, id, timestamp) { }
public MappedCustomer(CustomerData entity, string id, DateTime? timestamp)
: base([Link], TYPE, entity, id, timestamp) { }
}
}
For each mapping class, you need to create a bucket class. For details about bucket classes, see Bucket Classes.
using [Link];
namespace WooCommerceTest
{
public class WooCustomerEntityBucket : EntityBucketBase, IEntityBucket
{
public IMappedEntity Primary => Customer;
Implementing a Connector for an E-Commerce System | 67
You will now create a DAC with the configuration settings that will be used by the connector. For this DAC, you will
add the database table and include it in the customization project.
GO
IF NOT EXISTS (SELECT * FROM SYSOBJECTS WHERE NAME='BCBINDINGWOOCOMMERCE' AND
XTYPE='U')
BEGIN
CREATE TABLE [dbo].[BCBindingWooCommerce](
[CompanyID] [int] NOT NULL,
[BindingID] [int] NOT NULL,
[StoreBaseUrl] [nvarchar](50) NULL,
[StoreXAuthToken] [nvarchar](max) NULL,
[StoreXAuthClient] [nvarchar](max) NULL,
[StoreAdminUrl] [nvarchar](200) NULL,
[WooCommerceStoreTimeZone] [nvarchar](100) NULL,
[WooCommerceDefaultCurrency] [nvarchar](12) NULL,
[tstamp] [timestamp] NOT NULL,
CONSTRAINT [PK_BCBindingWooCommerce] PRIMARY KEY CLUSTERED
(
[CompanyID] ASC,
[BindingID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
You can generate the DAC from the table in the database by using the Customization Project
Editor (as described in To Create a New DAC) and move it to the extension library (as
described in To Move a Code Item to the Extension Library). Alternatively, you can create the
DAC directly in the Visual Studio project.
using [Link];
using [Link];
using [Link];
namespace WooCommerceTest
{
[PXCacheName("WooCommerce Settings")]
public class BCBindingWooCommerce : IBqlTable
{
public class PK : PrimaryKeyOf<BCBindingWooCommerce>.
By<[Link]>
{
public static BCBindingWooCommerce Find(PXGraph graph, int? binding) =>
FindBy(graph, binding);
}
#region BindingID
[PXDBInt(IsKey = true)]
[PXDBDefault(typeof([Link]))]
[PXUIField(DisplayName = "Store", Visible = false)]
[PXParent(typeof(Select<BCBinding, Where<[Link],
Equal<Current<[Link]>>>>))]
public int? BindingID { get; set; }
public abstract class bindingID : [Link]<bindingID> { }
#endregion
//Connection
#region StoreBaseUrl
[PXDBString(50, IsUnicode = true, InputMask = "")]
[PXUIField(DisplayName = "API Path")]
[PXDefault()]
public virtual string StoreBaseUrl { get; set; }
public abstract class storeBaseUrl :
[Link]<storeBaseUrl> { }
#endregion
#region StoreXAuthClient
[PXRSACryptString(IsUnicode = true, InputMask = "")]
[PXUIField(DisplayName = "Consumer Key")]
[PXDefault()]
public virtual string StoreXAuthClient { get; set; }
public abstract class storeXAuthClient :
[Link]<storeXAuthClient> { }
#endregion
#region StoreXAuthToken
[PXRSACryptString(IsUnicode = true, InputMask = "")]
Implementing a Connector for an E-Commerce System | 69
#region WooCommerceDefaultCurrency
[PXDBString(12, IsUnicode = true)]
[PXUIField(DisplayName = "Default Currency", IsReadOnly = true)]
public virtual string WooCommerceDefaultCurrency { get; set; }
public abstract class wooCommerceDefaultCurrency :
[Link]<wooCommerceDefaultCurrency> { }
#endregion
#region WooCommerceStoreTimeZone
[PXDBString(100, IsUnicode = true)]
[PXUIField(DisplayName = "Store Time Zone", IsReadOnly = true)]
public virtual string WooCommerceStoreTimeZone { get; set; }
public abstract class wooCommerceStoreTimeZone :
[Link]<wooCommerceStoreTimeZone> { }
#endregion
#region StoreAdminURL
[PXDBString(100, IsUnicode = true, InputMask = "")]
[PXUIField(DisplayName = "Store Admin Path")]
[PXDefault()]
public virtual string StoreAdminUrl { get; set; }
public abstract class storeAdminUrl :
[Link]<storeAdminUrl> { }
#endregion
}
}
You will now implement a REST API client of the external system.
The classes and the communication with the external system depend on the external system.
using [Link];
using [Link];
using [Link];
using [Link];
using BigCom = [Link];
namespace WooCommerceTest
{
public abstract class RestDataProviderBase
{
internal const string COMMERCE_RETRY_COUNT = "CommerceRetryCount";
protected const int BATCH_SIZE = 10;
protected const string ID_STRING = "id";
protected const string PARENT_ID_STRING = "parent_id";
protected const string OTHER_PARAM = "other_param";
protected readonly int commerceRetryCount =
[Link](COMMERCE_RETRY_COUNT, 3);
protected IWooRestClient _restClient;
public RestDataProviderBase()
{
}
int retryCount = 0;
while (true)
{
try
{
var request = _restClient.MakeRequest(GetListUrl,
urlSegments?.GetUrlSegments());
[Link] = [Link];
return result;
}
catch ([Link] ex)
{
if (ex?.ResponceStatusCode ==
default(HttpStatusCode).ToString() &&
retryCount < commerceRetryCount)
Implementing a Connector for an E-Commerce System | 71
{
_restClient.Logger?.ForContext("Scope", new BCLogTypeScope(
GetType()))
.Error("{CommerceCaption}: Operation failed, RetryCount
{
RetryCount}, Exception {ExceptionMessage}",
[Link], retryCount,
ex?.ToString());
retryCount++;
[Link](1000 * retryCount);
}
else throw;
}
}
}
int retryCount = 0;
while (true)
{
try
{
var request = _restClient.MakeRequest(GetListUrl,
urlSegments?.GetUrlSegments());
return result;
}
catch ([Link] ex)
{
if (ex?.ResponceStatusCode ==
default(HttpStatusCode).ToString() &&
retryCount < commerceRetryCount)
{
_restClient.Logger?.ForContext("Scope", new BCLogTypeScope(
GetType()))
.Error("{CommerceCaption}: Operation failed, RetryCount
{RetryCount}, Exception {ExceptionMessage}",
[Link], retryCount,
ex?.ToString());
retryCount++;
[Link](1000 * retryCount);
}
Implementing a Connector for an E-Commerce System | 72
else throw;
}
}
}
int retryCount = 0;
while (true)
{
try
{
var request = _restClient.MakeRequest(GetSingleUrl,
urlSegments?.GetUrlSegments());
return result;
}
catch ([Link] ex)
{
if (ex?.ResponceStatusCode ==
default(HttpStatusCode).ToString() &&
retryCount < commerceRetryCount)
{
_restClient.Logger?.ForContext("Scope",
new BCLogTypeScope(GetType()))
.Error("{CommerceCaption}: Operation failed, RetryCount
{RetryCount}, Exception {ExceptionMessage}",
[Link], retryCount,
ex?.ToString());
retryCount++;
[Link](1000 * retryCount);
}
else throw;
}
}
}
int retryCount = 0;
while (true)
{
try
{
var request = _restClient.MakeRequest(GetSingleUrl,
[Link]());
return result;
}
catch ([Link] ex)
{
if (ex?.ResponceStatusCode ==
default(HttpStatusCode).ToString() &&
retryCount < commerceRetryCount)
{
_restClient.Logger?.ForContext("Scope",
new BCLogTypeScope(GetType()))
.Error("{CommerceCaption}: Operation failed, RetryCount
{RetryCount}, Exception {ExceptionMessage}",
[Link], retryCount,
ex?.ToString());
retryCount++;
[Link](1000 * retryCount);
}
else throw;
}
}
}
return segments;
}
return segments;
}
protected static [Link] MakeUrlSegments(string id,
string parentId, string param)
{
var segments = new [Link]();
[Link](PARENT_ID_STRING, parentId);
[Link](ID_STRING, id);
[Link](OTHER_PARAM, param);
return segments;
}
}
}
namespace WooCommerceTest
{
public interface IFilter
{
void AddFilter(IRestRequest request);
int? Limit { get; set; }
int? Page { get; set; }
namespace WooCommerceTest
{
public class Filter : IFilter
{
protected const string RFC2822_DATE_FORMAT =
"{0:ddd, dd MMM yyyy HH:mm:ss} GMT";
protected const string ISO_DATE_FORMAT = "{0:yyyy-MM-ddTHH:mm:ss}";
[Description("per_page")]
public int? Limit { get; set; }
[Description("page")]
public int? Page { get; set; }
Implementing a Connector for an E-Commerce System | 75
[Description("offset")]
public int? Offset { get; set; }
[Description("order")]
public string Order { get; set; }
[Description("orderby")]
public string OrderBy { get; set; }
[Link](key, value);
}
}
}
}
}
namespace WooCommerceTest
{
public abstract class RestDataProvider : RestDataProviderBase
{
public RestDataProvider() : base()
{
TE entity = default;
while (needGet)
{
int retryCount = 0;
while (retryCount <= commerceRetryCount)
{
try
{
entity = Get<T, TE>(localFilter, urlSegments);
break;
}
catch (Exception ex)
{
_restClient.Logger?.ForContext("Scope",
new BCLogTypeScope(GetType()))
.Verbose("{CommerceCaption}: Failed at page {Page},
RetryCount {RetryCount}, Exception {ExceptionMessage}",
[Link],
[Link], retryCount, ex?.Message);
if (retryCount == commerceRetryCount)
throw;
retryCount++;
[Link](1000 * retryCount);
}
}
Implementing a Connector for an E-Commerce System | 77
[Link]++;
}
}
namespace WooCommerceTest
{
public class CustomerDataProvider : RestDataProvider
{
protected override string GetListUrl { get; } = "/customers";
Implementing a Connector for an E-Commerce System | 78
2. Create a class for the REST client of the external system, which defines the methods that you need to
process REST requests to the external system. In this example, you are creating the WooRestClient class,
shown in the following code.
using [Link];
using RestSharp;
using [Link];
using [Link];
using System;
Implementing a Connector for an E-Commerce System | 79
using [Link];
using [Link];
using BigCom = [Link];
namespace WooCommerceTest
{
public class WooRestClient : WooRestClientBase, IWooRestClient
{
public WooRestClient(IDeserializer deserializer, ISerializer serializer,
[Link] options, [Link] logger)
: base(deserializer, serializer, options, logger)
{
}
[Link] = [Link];
var response = Execute<T>(request);
if ([Link] == [Link] ||
[Link] == [Link] ||
[Link] == [Link])
{
T result = [Link];
return result;
}
return result;
}
return result;
}
return result;
}
if ([Link] == [Link] ||
[Link] == [Link] ||
[Link] == [Link])
Implementing a Connector for an E-Commerce System | 81
{
T result = [Link];
return result;
}
if ([Link] == [Link] ||
[Link] == [Link])
{
TE result = [Link];
return result;
}
In this example, the following supplementary classes and interfaces are used, which are shown in the
following code fragments:
• WooRestClientBase
using System;
using [Link];
using [Link];
using [Link];
using [Link];
Implementing a Connector for an E-Commerce System | 82
using RestSharp;
using [Link];
using [Link];
namespace WooCommerceTest
{
public abstract class WooRestClientBase : RestClient
{
protected ISerializer _serializer;
protected IDeserializer _deserializer;
public [Link] Logger { get; set; } = null;
protected WooRestClientBase(IDeserializer deserializer,
ISerializer serializer, IRestOptions options,
[Link] logger)
{
_serializer = serializer;
_deserializer = deserializer;
AddHandler("application/json", () => deserializer);
AddHandler("text/json", () => deserializer);
AddHandler("text/x-json", () => deserializer);
try
{
BaseUrl = new Uri([Link]);
}
catch (UriFormatException e)
{
throw new UriFormatException(
"Invalid URL: The format of the URL could not be determined.",
e);
}
Logger = logger;
}
if (urlSegments != null)
{
foreach (var urlSegment in urlSegments)
{
[Link]([Link], [Link]);
}
}
return request;
}
{
//Get the values of the parameters passed to the API
var parameters = [Link](", ", [Link](
x => [Link]() + "=" + ([Link] ?? "NULL")).ToArray());
• IWooRestClient
using RestSharp;
using Serilog;
using [Link];
namespace WooCommerceTest
{
public interface IWooRestClient
{
RestRequest MakeRequest(string url,
Dictionary<string, string> urlSegments = null);
• IWooEntity
using System;
namespace WooCommerceTest
{
public interface IWooEntity
Implementing a Connector for an E-Commerce System | 84
{
DateTime? DateCreatedUT { get; set; }
• Authenticator
using RestSharp;
using [Link];
using [Link];
namespace WooCommerceTest
{
public class Autentificator : IAuthenticator
{
private readonly string _consumerKey;
private readonly string _consumerSecret;
return request;
}
}
}
Implementing a Connector for an E-Commerce System | 85
3. Create the connector class and define the GetRestClient static methods in it, as shown in the following
code.
using [Link];
using System;
using [Link];
using [Link];
using [Link];
using CommonServiceLocator;
namespace WooCommerceTest
{
public class WooCommerceConnector
{
public static WooRestClient GetRestClient(BCBindingWooCommerce binding)
{
return GetRestClient([Link], [Link],
[Link]);
}
return client;
}
}
}
For each pair of entities that you need to synchronize, you need to create a processor class. For details about
processor classes, see Processor Classes.
using [Link];
using [Link];
using [Link];
using [Link];
using [Link];
using System;
using [Link];
using [Link];
namespace WooCommerceTest
{
[BCProcessor(typeof(WooCommerceConnector), [Link],
[Link],
IsInternal = false,
Direction = [Link],
PrimaryDirection = [Link],
PrimarySystem = [Link],
PrimaryGraph = typeof([Link]),
ExternTypes = new Type[] { typeof(CustomerData) },
LocalTypes = new Type[] { typeof([Link]) },
AcumaticaPrimaryType = typeof([Link]),
AcumaticaPrimarySelect = typeof([Link]),
URL = "[Link]?user_id={0}"
)]
[BCProcessorRealtime(PushSupported = false, HookSupported = false)]
public class WooCustomerProcessor : BCProcessorSingleBase<WooCustomerProcessor,
WooCustomerEntityBucket, MappedCustomer>, IProcessor
{
public WooRestClient client;
protected CustomerDataProvider customerDataProvider;
#region Constructor
public override void Initialise(IConnector iconnector,
ConnectorOperation operation)
Implementing a Connector for an E-Commerce System | 87
{
[Link](iconnector, operation);
client = [Link](
GetBindingExt<BCBindingWooCommerce>());
customerDataProvider = new CustomerDataProvider(client);
}
#endregion
#region Import
public override void MapBucketImport(WooCustomerEntityBucket bucket,
IMappedEntity existing)
{
MappedCustomer customerObj = [Link];
[Link] = GetBillingCustomerName([Link]).
ValueField();
[Link] = [Link]([Link],
GetBinding().BindingName).ValueField();
[Link] = [Link] == null ||
existing?.Local == null ?
GetBindingExt<BCBindingExt>().CustomerClassID?.ValueField() : null;
//Primary Contact
[Link] = GetPrimaryContact([Link]);
[Link] = SetBillingContact([Link]);
[Link] = [Link]();
[Link] = [Link]();
[Link] = SetShippingContact([Link]);
}
}
[Link] =  ?
[Link]() :
 &&
[Link]([Link]) ?
[Link]() : [Link]();
[Link] = [Link]();
return contact;
}
return contactImpl;
}
return contactImpl;
}
? [Link]
: $"{[Link]?.FirstName} {[Link]?.LastName}";
}
return address;
}
return result;
}
#region Export
#region Pull
public override MappedCustomer PullEntity(Guid? localID,
Dictionary<string, object> externalInfo)
{
throw new NotImplementedException();
}
public override MappedCustomer PullEntity(string externID,
string externalInfo)
{
throw new NotImplementedException();
}
return status;
}
return status;
}
#endregion
}
public static class PhoneTypes
{
public static string Business1 = "B1";
}
}
For the system to create processor classes for all entities used by the specified connector, you need to define the
processor factory class. For details about the processor factory class, see Processor Factory Class.
2. In the class, specify the type of the connector in the ConnectorType property and implement the
GetProcessorTypes() method, which returns the list of processors of the connector, as shown in the
following code.
using System;
using [Link];
using [Link];
namespace WooCommerceTest
{
public class WooCommerceProcessorsFactory : IProcessorsFactory
{
public string ConnectorType => [Link];
In the connector class, which you have already created, you will now implement the synchronization of the
Acumatica ERP entities and the external entities. For a detailed description of the connector class, see Connector
Class.
using [Link];
using System;
using [Link];
using [Link];
using [Link];
using CommonServiceLocator;
using [Link];
using [Link];
using [Link];
using [Link];
using [Link];
namespace WooCommerceTest
{
public class WooCommerceConnector: BCConnectorBase<WooCommerceConnector>,
IConnector
{
public const string TYPE = "WOO";
public const string NAME = "WooCommerce";
Implementing a Connector for an E-Commerce System | 93
try
{
List<TInfo> result = new List<TInfo>();
return result;
}
catch (Exception ex)
{
LogError(new BCLogTypeScope(typeof(WooCommerceConnector)), ex);
}
return null;
}
if ([Link](bCBindingBigCommerce?.StoreAdminUrl) ||
[Link]([Link])) return;
{
LogInfo([Link](), [Link], NAME);
return dtLocal;
}
MissingMemberHandling = [Link],
NullValueHandling = [Link],
DefaultValueHandling = [Link],
DateFormatHandling = [Link],
DateTimeZoneHandling = [Link],
ContractResolver = new GetOnlyContractResolver()
};
RestJsonSerializer restSerializer = new RestJsonSerializer(serializer);
WooRestClient client = new WooRestClient(restSerializer, restSerializer,
options,
[Link]<[Link]>());
return client;
}
}
}
You need to create a connector descriptor class, which is required for the retrieval of external fields and for the
implementation of work with push notifications. For details about the connector descriptor class, see Connector
Descriptor Class.
using System;
using [Link];
using [Link];
namespace WooCommerceTest
{
public class WooCommercesConnectorDescriptor : IConnectorDescriptor
{
protected IList<EntityInfo> _entities;
{
List<Tuple<string, string, string>> fieldsList =
new List<Tuple<string, string, string>>();
if (entity != [Link] &&
entity != [Link]) return fieldsList;
return fieldsList;
}
}
}
For the system to create the connector class, you need to implement the connector factory class. For details about
the connector factory class, see Connector Factory Class.
using [Link];
using [Link];
using [Link];
namespace WooCommerceTest
{
public class WooCommerceConnectorFactory :
BaseConnectorFactory<WooCommerceConnector>, IConnectorFactory
{
public override string Description => [Link];
public override bool Enabled => true;
For a user to specify the basic settings of the connection with the external system, you need to create a custom
form in Acumatica ERP. You have the following options for the creation of the form:
• You can create a custom form from scratch. For details about the creation of custom forms, see To Develop a
Custom Form in the Customization Guide, or review the T200 Maintenance Forms training course.
• You can use the configuration forms that are available for existing BigCommerce and Shopify connectors as
a template and adjust these forms. In this step, you will use this approach.
using [Link];
using [Link];
using [Link];
using System;
using [Link];
using [Link];
namespace WooCommerceTest
{
public class WooCommerceStoreMaint : BCStoreMaint
{
public SelectFrom<BCBindingWooCommerce>.
Where<[Link].
IsEqual<[Link]>>.View
CurrentBindingWooCommerce;
public WooCommerceStoreMaint()
{
[Link]<Where<[Link].
IsEqual<[Link]>>>();
}
#region Actions
public PXAction<BCBinding> TestConnection;
[PXButton]
[PXUIField(DisplayName = "Test Connection", Enabled = false)]
Implementing a Connector for an E-Commerce System | 98
[Link](this, delegate
{
SystemStatusProvider restClient = new SystemStatusProvider(
[Link](bindingWooCommerce));
WooCommerceStoreMaint graph =
[Link]<WooCommerceStoreMaint>();
[Link] = binding;
[Link] = bindingWooCommerce;
try
{
var systemStatus = [Link]();
[Link] =
[Link];
[Link] =
[Link];
[Link]();
if (systemStatus == null)
throw new PXException([Link]);
[Link](
binding, nameof(BCBindingWooCommerce.
wooCommerceDefaultCurrency),
[Link]);
[Link](binding,
nameof([Link]),
[Link]);
[Link] = true;
[Link](
bindingWooCommerce);
[Link]();
}
catch (Exception ex)
{
throw new PXException(ex,
[Link], [Link]);
}
});
return [Link]();
}
Implementing a Connector for an E-Commerce System | 99
#endregion
[Link](row,
nameof([Link]),
[Link]?.Currency);
[Link](row,
nameof([Link]),
[Link]?.DefaultTimezone);
[Link] = true;
[Link](row);
}
catch (Exception) { }
}
//Actions
[Link]([Link] > 0 &&
[Link] == [Link]);
}
{
base._(e);
}
}
6. Add the messages that you are using in the actions and events of the graph to a class with the
PXLocalizable attribute, as shown in the following code.
using [Link];
namespace WooCommerceTest
{
[PXLocalizable]
class Messages
{
public const string TestConnectionStoreNotFound =
"The store data cannot be retrieved through the WooCommerce REST API.
Check that the store URL is correct.";
}
}
7. In the [Link] file, change the properties of the PXDataSource control as follows:
• TypeName: Assign the name of the graph that you have just created, such as
[Link].
• PrimaryView: Assign the name of the primary view of the graph, such as Bindings.
See the following code example.
8. Change the DataMember property of the PXFormView control to the name of the primary view of the
graph, such as Bindings. See the following code example.
9. Adjust the DataMember property and the list of fields of the PXTabItem control that corresponds to the
Connection Settings tab, as shown in the following code.
[Link] unnecessary elements from the PXTabItem control that corresponds to the Customer Settings
tab, as shown in the following code.
[Link] the PXTabItem controls for the tabs that you do not need to use.
• You can select multiple custom files to add them to the project at the same time.
• For any files other than the ones placed in the Bin folder, you can click Refresh on the
toolbar of the Add Files dialog box to make the system update the list of files in the table. If
you have changed files in the Bin folder of the website, you should refresh the page in the
browser.
5. In the dialog box, click Save to save each selected file to the customization project as a File item.
7. Open the Commerce workspace. Notice that the WooCommerce Stores link is now available in the
workspace under Configuration.
The Add Site Map dialog box displays all the custom site map nodes that have been created in
the site map of Acumatica ERP and the nodes that have been modified in the site map. You can
select multiple custom site map nodes to add them to the project simultaneously.
5. In the dialog box, click Save to add each selected site map node to the customization project.
Now that you have completed the development of the connector, you will test the connector. For the testing, you
need a WordPress site with the WooCommerce plug-in installed. The instructions below describe the testing of the
WooCommerce Stores (WO201000) form.
• API Path (in the REST Settings section): The request URL, including the version number, such as https://
[Link]/wp-json/wc/v3
• Consumer Key (in the REST Settings section): The API key that you have created on the WooCommerce
admin portal
• Consumer Secret (in the REST Settings section): The secret for the API key that you have created on the
WooCommerce admin portal
5. On the form toolbar, click Test Connection. If the connection is successful, the Default Currency and Store
Time Zone boxes in the Store Properties section are filled with the values from the store.
6. On the Entity Settings tab, select the Active check box for the Customer entity.
7. On the Customer Settings tab, specify values in the following boxes:
• Customer Class: The customer class that is assigned to new customers imported to Acumatica ERP from
the WooCommerce store
• Customer Auto-Numbering: The numbering sequence that the system uses for generating identifiers for
imported customers
8. Save your settings.
9. On the Prepare Data (BC501000) form, prepare the customer data for import as follows:
a. In the Entity box, select Customer.
b. In the table, select the Selected check box for the row with the Customer entity.
c. On the form toolbar, click Prepare.
d. In the Prepared Records column, click the link. The Process Data (BC501500) form opens.
[Link] the Process Data form, in the table, select the unlabeled check box for the customer records that should
be saved in Acumatica ERP.
[Link] the form toolbar, click Process.
[Link] the Customers (AR303000) form, review the imported customers.
To implement push notifications for a commerce connector, you need to perform the basic steps that are described
in this chapter.
The chapter presents an example of the integration of Acumatica ERP with WooCommerce in which you need
to export customers from Acumatica ERP to a WooCommerce store in real-time mode. This example is based on
the code and customization project prepared in To Create a Connector for an External System. In the Help-and-
Training-Examples repository on GitHub, you can find the files that are modified in this code to prepare the example
described in this chapter.
You need to create generic inquiries or select existing ones that will define the data whose changes trigger sending
notifications from Acumatica ERP to a commerce connector.
2. If the list of generic inquiries is sufficient for your connector or you need to add multiple generic inquiries
for the entities that you want the system to send push notifications for, use the predefined Commerce
notification destination, skip the next instruction, and proceed with 2. Selecting the Generic Inquiries for
Real-Time Synchronization.
3. If you need a completely different list of generic inquiries for the entities that you want the system to send
push notifications for, create a custom notification destination as follows:
a. In the Destination Name box, type the name of the target notification destination, which can be the
name of your external application.
b. In the Destination Type box, select Commerce Push Destination. The Address box is filled in
automatically with Commerce.
c. Save your changes.
If all fields in the results of the generic inquiry are tracked, the system produces push
notifications for changes of any field in the results of the generic inquiry, which can
cause overflow of the push notification queue. If you need to track only particular fields
in the results of the generic inquiry, push notifications for changes in other fields are
useless for you but consume system resources. Therefore, we recommend that you
specify particular fields to be tracked in the Fields table.
• If you need to track only particular fields in the results of the generic inquiry, while the row of the
Inquiries table is still selected, do the following in the Fields table for each field that you need to
track:
a. On the table toolbar, click Add Row.
b. In the Table Name column of the added row, select the name of the table that contains the field
that the system should track.
c. In the Field Name column, select the name of the field that the system should track.
4. On the form toolbar, click Save.
5. Add the generic inquiries that you included in the notification definition to the customization project
of the extension library for your commerce connector. For details about adding a generic inquiry to the
customization project, see To Add a Generic Inquiry to a Project.
Implementing a Connector for an E-Commerce System | 106
6. Include the notification definition in the customization project of your extension library. For details, see To
Add Push Notification Definitions to a Project.
For the entities for which you need to support real-time synchronization through push notifications, you need
to adjust the connector descriptor class and the processor classes that correspond to these entities so that
the processor classes support real-time synchronization. For details about processor classes, see Processor
Classes. For information about the implementation of connector descriptor and processor classes, see Step
11: Implementing the Connector Descriptor Class and Step 8: Implementing the Processor Classes of To Create a
Connector for an External System.
2. For each processor class that needs to support real-time synchronization through push notifications, make
sure that the processor class supports the export of records from Acumatica ERP by checking the following
requirements:
• The BCProcessor attribute of the processor has the Direction property set to
[Link] or [Link].
• The FetchBucketsForExport(), GetBucketForExport(), MapBucketExport(),
SaveBucketExport(), and PullEntity() methods of the processor have been implemented. An
example of the implementation of the PullEntity() method is shown in the following code.
public override MappedCustomer PullEntity(Guid? localID,
Dictionary<string, object> fields)
{
[Link] impl =
[Link]<[Link]>(localID);
if (impl == null) return null;
return obj;
Implementing a Connector for an E-Commerce System | 107
3. For each processor class that needs to support real-time synchronization through push notifications, modify
the BCProcessorRealtime attribute of the processor so that it has the PushSupported property
set to true. In the PushSources property specify the names of the generic inquiries to be used for push
notifications. In the PushDestination property, specify the name of the push notification destination,
which is Commerce if you use the predefined notification destination.
The following code shows an example of the attribute for the customer processor class.
...
[BCProcessorRealtime(PushSupported = true, HookSupported = false,
PushSources = new String[] { "BC-PUSH-Customers" },
PushDestination = [Link]
)]
public class WooCustomerProcessor : BCProcessorSingleBase<WooCustomerProcessor,
WooCustomerEntityBucket, MappedCustomer>, IProcessor
{
...
}
Related Links
• Real-Time Synchronization Through Push Notifications
Now that you have completed the implementation of push notifications for the connector, you will test them. For
the testing, you need a WordPress site with the WooCommerce plug-in installed. The instructions below are based
on the assumption that you have already configured a WooCommerce store, as described in Step 14: Testing the
Connector of To Create a Connector for an External System.
Once you have started the real-time synchronization, the system automatically activates the
notification destination (which is specified in the BCProcessorRealtime attribute of the
processor that corresponds to the entity) and the generic inquiry that corresponds to the
entity on the Push Notifications (SM302000) form.
3. On the Customers (AR303000) form, modify a customer record (for example, change the account name) and
save your changes.
4. Wait for 20 seconds for the push notification to be processed.
Implementing a Connector for an E-Commerce System | 108
5. On the Sync History (BC301000), make sure the modified customer record has appeared in the list on the
Ready to Process tab.
Related Links
• Real-Time Synchronization Through Push Notifications