Giới Thiệu
Trong thế giới thương mại điện tử B2B, việc tích hợp giữa hệ thống quản lý nội dung (CMS) và hệ thống quản lý doanh nghiệp (ERP) là một bài toán phức tạp nhưng vô cùng quan trọng. Bài viết này chia sẻ kinh nghiệm thực tế từ việc tích hợp SAP ERP vào nền tảng Optimizely Commerce (trước đây là Episerver) trong dự án thương mại điện tử cho ngành thiết bị nha khoa — nơi mà tính chính xác về giá, thuế, và quản lý đơn hàng là yếu tố sống còn.
Khi làm việc với các khách hàng B2B quy mô lớn, chúng ta thường gặp phải các hệ thống cũ (legacy) dựa trên các giao thức đã được thiết lập lâu đời. Hướng dẫn này khám phá cách thu hẹp khoảng cách giữa kiến trúc .NET hiện đại và cấu trúc backend mạnh mẽ nhưng cứng nhắc của SAP, đảm bảo trải nghiệm mượt mà cho cả người dùng cuối và nhân viên quản trị.
1. Tổng Quan Kiến Trúc
1.1 Bức Tranh Toàn Cảnh
Kiến trúc tích hợp được thiết kế theo mô hình layered architecture (kiến trúc phân lớp). Điều này đảm bảo sự tách biệt rõ ràng giữa các thành phần, giúp hệ thống dễ bảo trì, kiểm thử và mở rộng. Luồng dữ liệu di chuyển từ Frontend Episerver qua một lớp SAP Service chuyên dụng trước khi giao tiếp với SAP ERP qua SOAP/HTTPS.
Các tầng chính bao gồm:
- Frontend Layer: Xử lý tương tác người dùng như Giỏ hàng và trang Thanh toán.
- Application Layer: Chứa logic nghiệp vụ, các handler MediatR và các dịch vụ chuyển đổi dữ liệu.
- Integration Layer: Điểm giao tiếp trực tiếp với SAP sử dụng các SOAP client được tạo tự động.
- External Layer: SAP ERP (ECC hoặc S/4HANA) cung cấp dữ liệu nghiệp vụ thực tế.
1.2 Các Thành Phần Chính
Để duy trì một hệ thống mạnh mẽ, chúng tôi đã phân loại các thành phần theo các vai trò cụ thể như bảng dưới đây:
| Thành Phần | Vai Trò | File/Namespace Chính |
|---|---|---|
| SAP Module | Giao tiếp trực tiếp với SAP qua giao thức SOAP. | SAP/OrderService.cs, SAP/CustomerService.cs |
| Checkout Services | Lớp trung gian chuyển đổi đối tượng Commerce sang DTO cho SAP. | Features/Checkout/Services/SAPOrderService.cs |
| Event Handlers | Xử lý các sự kiện sau khi đặt hàng theo mô hình CQRS. | Features/Checkout/Events/SendOrderToSAP.cs |
| Domain Models | Cấu trúc dữ liệu dùng chung cho đơn hàng, địa chỉ và sản phẩm. | SAP/Domain/SAPLineItem.cs, SAPAddress.cs |
| Error Handling | Quản lý mã trả về từ SAP và thông báo cho admin. | SAPError.cs, SendEmailToSAPAdmins.cs |
2. Giao Tiếp với SAP qua SOAP Web Services
2.1 Tại Sao Lại Là SOAP?
Các hệ thống SAP ERP truyền thống (đặc biệt là ECC) sử dụng BAPI (Business Application Programming Interface). Các BAPI này thường được expose dưới dạng SOAP Web Services thông qua nền tảng SAP NetWeaver. Mặc dù REST là tiêu chuẩn hiện đại, SOAP vẫn là con đường tích hợp phổ biến và ổn định nhất cho môi trường SAP ECC nhờ vào cách tiếp cận strict contract-first (WSDL).
Trong dự án nha khoa này, chúng tôi đã sử dụng hai endpoint SOAP chính:
- SAPOrderService: Xử lý tất cả các thao tác liên quan đến đơn hàng bao gồm Mô phỏng (giá/thuế), Tạo đơn hàng và Lịch sử đơn hàng.
- SAPCustomerService: Quản lý dữ liệu khách hàng, bao gồm tạo mới, đồng bộ địa chỉ và kiểm tra hạn mức tín dụng.
2.2 Cấu Hình Kết Nối
Kết nối đến SAP được thiết lập thông qua BasicHttpBinding. Do tính chất nhạy cảm của các giao dịch B2B, chúng tôi đã sử dụng bảo mật Transport (HTTPS) kết hợp với Basic Authentication.
private serviceSoapClient GetSapOrderSoapClient()
{
var binding = new BasicHttpBinding
{
Security =
{
Mode = BasicHttpSecurityMode.Transport,
Transport = { ClientCredentialType = HttpClientCredentialType.Basic }
},
MaxReceivedMessageSize = 20000000, // 20MB cho các response lớn
MaxBufferSize = 20000000,
SendTimeout = TimeSpan.FromSeconds(90),
Name = "serviceSoap"
};
var address = new EndpointAddress(_sapOrderEndpoint);
var orderClient = new serviceSoapClient(binding, address);
var credentials = orderClient.ClientCredentials;
credentials.UserName.UserName = _sapUsername;
credentials.UserName.Password = _sapPassword;
return orderClient;
}
Lưu Ý Quan Trọng Khi Cấu Hình:
- Kích Thước Buffer: Chúng tôi cấu hình buffer lên đến 20MB. Các phản hồi từ SAP cho việc đồng bộ khách hàng hoặc lịch sử đơn hàng lớn có thể cực kỳ đồ sộ.
- Thời Gian Chờ (Timeout): Cần thiết lập timeout 90 giây. Việc xử lý của SAP ERP, đặc biệt là khi tính toán các quy tắc thuế phức tạp cho thiết bị nha khoa qua nhiều khu vực, có thể mất nhiều thời gian.
- Quản Lý Bí Mật: Thông tin xác thực nên được lưu trong
appsettings.jsonvà inject quaIConfiguration, tốt nhất là sử dụng Azure Key Vault cho môi trường production.
3. Quản Lý Đơn Hàng Real-Time
3.1 Order Simulation: Tính Giá Chính Xác
Trong B2B, giá niêm yết trên CMS thường chỉ là "giá sàn". Giá thực tế khách hàng phải trả phụ thuộc vào hợp đồng cụ thể, chiết khấu theo số lượng và thuế khu vực được lưu trong SAP. Order Simulation cho phép frontend gửi giỏ hàng hiện tại sang SAP để nhận về giá "Cuối cùng" mà không tạo ra một bản ghi vĩnh viễn.
Khi khách hàng đến bước tóm tắt thanh toán, hệ thống kích hoạt SimulateOrder(address, lineItems). Điều này đảm bảo shipmentTotal và taxTotal chính xác 100% theo logic nội bộ của SAP.
// Chuyển đổi Line Item từ Episerver sang định dạng SAP
private List<SAPLineItem> ConvertToSAPLineItems(IEnumerable<ILineItem> lineItems)
{
var sapLineItems = new List<SAPLineItem>();
var index = 10; // Quy ước SAP: items bắt đầu từ 10, tăng dần 10
foreach (var lineItem in lineItems)
{
var variationCode = lineItem.Code;
var sapCode = variationCode.StartsWith("_") ? variationCode.Substring(1) : variationCode;
sapLineItems.Add(new SAPLineItem
{
ItemNum = index.ToString(),
VariantCode = sapCode,
OrderedQty = lineItem.Quantity,
WebPrice = lineItem.PlacedPrice,
IsProduct = !lineItem.Properties[Constants.CommerceFields.IsService].ToBool()
});
index += 10;
}
return sapLineItems;
}
3.2 Tạo Đơn Hàng và Khả Năng Chịu Lỗi
Khi khách hàng nhấn "Đặt hàng", hệ thống chuyển từ mô phỏng sang tạo thực tế. Chúng tôi sử dụng thư viện MediatR để xử lý việc này như một sự kiện tách biệt. Nếu SAP tạm thời không khả dụng, hệ thống không được phép bị sập; nó phải xử lý lỗi một cách mềm dẻo.
public async Task Handle(OrderPlaced notification, CancellationToken cancellationToken)
{
var sapConfiguration = _settingsProvider.GetSapConfiguration();
if (!sapConfiguration.SapRealTimeOrder)
{
await _mediator.Publish(new StaffOrderNotification(notification.PurchaseOrder));
return;
}
var errors = _sapOrderService.CreateOrder(..., out sapOrderNumber);
if (errors.Any())
{
// Đánh dấu đơn hàng chờ xử lý thủ công và báo cho admin
notification.PurchaseOrder.Status = OrderStatus.AwaitingExchange.ToString();
await _emailToSapAdmins.SendEmailToAdmins(errors, ...);
}
else
{
// Thành công: Lưu mã tham chiếu SAP
notification.PurchaseOrder[Constants.CommerceFields.SAPOrderNumber] = sapOrderNumber;
await _mediator.Send(new SaveOrder(notification.PurchaseOrder));
}
}
4. Đồng Bộ Hóa Khách Hàng Hai Chiều
Dữ liệu khách hàng trong môi trường B2B thường bắt nguồn từ SAP ("Nguồn Sự Thật"). Tuy nhiên, người dùng lại cập nhật hồ sơ của họ trên Optimizely. Giữ cho chúng đồng bộ là một thách thức lớn.
4.1 Khớp Địa Chỉ Thông Minh
Khi đồng bộ địa chỉ từ SAP ngược về Episerver, chúng tôi sử dụng logic khớp hai bước để ngăn chặn việc tạo trùng địa chỉ:
- Khớp Chính Xác: Dựa trên
SAPAddressNumber(ID duy nhất từ SAP). - Khớp Fuzzy: Nếu thiếu số ID SAP, chúng tôi khớp theo tổ hợp
Thành phố + Mã bưu điện + Địa chỉ đường.
4.2 Quản Lý Danh Tính với Azure AD B2C
Khi một khách hàng mới đăng ký, chúng tôi không chỉ tạo bản ghi trong Episerver và SAP. Chúng tôi còn cập nhật Azure AD B2C. Việc lưu trữ SAPCustomerNumber dưới dạng custom attribute trong B2C cho phép xác thực liền mạch và đảm bảo danh tính người dùng nhất quán trên tất cả các ứng dụng doanh nghiệp.
5. Thông Tin Chiến Lược & Xử Lý Sự Cố
5.1 Xử Lý Các Thông Báo Trả Về Từ SAP
SAP không sử dụng mã trạng thái HTTP cho các lỗi logic nghiệp vụ. Thay vào đó, nó trả về một trường TYPE trong response:
- Type "S": Success (Thành công).
- Type "W": Warning (Cảnh báo - đơn hàng vẫn được tạo nhưng cần lưu ý).
- Type "E": Error (Lỗi - đơn hàng thất bại).
5.2 Xử Lý Lỗi Timeout SOAP
Nếu bạn gặp phải TaskCanceledException hoặc TimeoutException thường xuyên, hãy cân nhắc:
- Triển Khai Chính Sách Thử Lại (Retry Policy): Sử dụng Polly để thực hiện exponential backoff.
- Circuit Breaker: Nếu SAP đang bảo trì, hãy sử dụng Circuit Breaker để ngừng gửi yêu cầu ngay lập tức, tránh làm cạn kiệt thread pool của web server.
- Log File XML: Các vấn đề tích hợp SAP gần như không thể debug nếu không có log SOAP XML thô. Hãy sử dụng
IClientMessageInspectortùy chỉnh để log mọi request và response.
6. Kết Luận
Tích hợp SAP ERP với Optimizely Commerce là một hành trình đòi hỏi sự hiểu biết sâu sắc về cả thực tiễn web hiện đại và logic doanh nghiệp kế thừa. Bằng cách triển khai kiến trúc phân lớp, sử dụng MediatR để xử lý theo hướng sự kiện và quản lý đồng bộ dữ liệu với logic khớp mạnh mẽ, bạn có thể xây dựng một hệ thống vừa bền bỉ vừa có khả năng mở rộng.
Bài học then chốt từ dự án thiết bị nha khoa của chúng tôi là: Đừng bao giờ tin tưởng một phản hồi mà không có kiểm chứng. Dù là tính phí vận chuyển hay số tiền thuế, hãy luôn triển khai các bước kiểm tra an toàn để đảm bảo dữ liệu chảy vào CMS của bạn là hợp lý và nhất quán.