Tools & Workflow

Cách Chúng Tôi Kết Nối E-Commerce với SAP (và Những Bài Học Xương Máu)

By Ginbok5 min read

Vấn Đề Đặt Ra

Nếu bạn đã từng làm việc với một nền tảng e-commerce cần kết nối với SAP, bạn sẽ hiểu ngay cái "vui vẻ" đó: hai thế giới hoàn toàn khác nhau về cách nhìn nhận dữ liệu, thời gian xử lý, và định nghĩa của một "lỗi." Một bên là ứng dụng web hiện đại cần phản hồi dưới một giây trong quá trình thanh toán. Bên kia là hệ thống ERP được thiết kế cho xử lý batch và quy trình back-office, giao tiếp qua SOAP.

Bài viết này trình bày kiến trúc chúng tôi đã xây dựng để kết nối hai thế giới đó — luồng dữ liệu từ storefront đến SAP, cách xử lý lỗi, và các quyết định thiết kế chúng tôi sẽ làm lại hoặc làm khác đi.

Tổng Quan Kiến Trúc: 3 Tầng

┌──────────────────────────────────────────────┐
│   Web Layer (Controllers / API Endpoints)    │
│   Xử lý HTTP request, auth, routing          │
├──────────────────────────────────────────────┤
│   Application Layer (Domain Services)        │
│   Điều phối business logic, mapping models   │
├──────────────────────────────────────────────┤
│   ERP Integration Layer (SOAP Clients)       │
│   Chuyển đổi domain objects → SOAP calls     │
│   Quản lý credentials, timeout, retry        │
└──────────────────────────────────────────────┘

Điểm mấu chốt: tầng tích hợp là project riêng biệt, độc lập. Khi SAP thay đổi WSDL (và chắc chắn sẽ thay đổi), phạm vi ảnh hưởng chỉ nằm trong một project duy nhất.

Luồng Dữ Liệu: Từ Giỏ Hàng Đến SAP

Quy Trình 2 Giai Đoạn

Giai đoạn 1 — Mô phỏng đơn hàng (trong lúc checkout)

[Giỏ hàng] → [YourOrderService].SimulateOrder()
    → Chuyển đổi address DTO → [ErpAddress]
    → Chuyển đổi line items → [ErpLineItem[]]
    → Load cấu hình ERP theo locale
    → Gọi ERP SOAP endpoint: SimulateOrder
    → Parse response → lấy tổng thuế + phí vận chuyển
    → Trả về giao diện checkout

Giai đoạn 2 — Tạo đơn hàng (sau khi thanh toán)

[OrderPlaced Event] → [YourOrderHandler].Handle()
    → Load cấu hình ERP theo locale
    → Kiểm tra real-time ordering có được bật không
    → Tạo địa chỉ giao hàng + thanh toán
    → Gọi ERP SOAP endpoint: CreateOrder
    → Thành công: lưu mã đơn hàng ERP
    → Thất bại: cập nhật trạng thái, thông báo admin

Tách Biệt Bằng Event-Driven

// Minh họa — không phải code production thực tế
public class [YourOrderHandler] : INotificationHandler<[OrderPlacedEvent]>
{
    public async Task Handle([OrderPlacedEvent] notification, CancellationToken ct)
    {
        var erpConfig = _configProvider.GetErpConfig(currentLocale);
        if (erpConfig == null || !erpConfig.RealTimeOrderEnabled)
        {
            await _mediator.Publish(new [StaffNotification](order), ct);
            return;
        }
        var errors = _erpOrderService.CreateOrder(
            shippingAddress, billingAddress, lineItems,
            paymentInfo, erpConfig, out string erpOrderNumber);
        if (errors.Any())
            await HandleErpFailure(order, errors, ct);
        else
            await HandleErpSuccess(order, erpOrderNumber, ct);
    }
}

Sự tách biệt này rất quan trọng: luồng checkout không bị chặn bởi SAP. Nếu SAP bị down, thanh toán của khách hàng không bị ảnh hưởng.

SOAP Client: Cấu Hình Quan Trọng Hơn Bạn Nghĩ

private [ErpSoapClient] BuildSoapClient()
{
    var binding = new BasicHttpBinding
    {
        Security = {
            Mode = BasicHttpSecurityMode.Transport,
            Transport = { ClientCredentialType = HttpClientCredentialType.Basic }
        },
        MaxReceivedMessageSize = 20_000_000,
        SendTimeout = TimeSpan.FromSeconds(90)
    };
    var client = new [ErpSoapClient](binding, new EndpointAddress(_erpEndpoint));
    client.ClientCredentials.UserName.UserName = _erpUsername;
    client.ClientCredentials.UserName.Password = _erpPassword;
    return client;
}

Xử Lý Lỗi: Phân Loại Các Kiểu Lỗi SAP

1. Validation Trước Khi Gọi

if (config.SalesOrganization.IsNullOrEmpty())
{
    errors.Add(new [ErpMessage]("E", "", "Thiếu cấu hình ERP cho locale này"));
    return errors;
}

2. Lỗi Từ SOAP Response

var fatalErrors = erpResponse.Messages
    .Where(m => m.Type == "E" || m.Type == "Error")
    .Where(m => !(m.Number == "219" && m.Category == "V4"))  // Non-fatal đã biết
    .Select(m => new [ErpMessage](m.Type, m.Number, m.Text));

3. Null Response & 4. Exception

Mọi tầng của response phải được null-check phòng thủ. Network timeout và SOAP fault được bắt ở tầng cao nhất và kích hoạt pipeline thông báo admin.

Pipeline Thông Báo Admin

Đồng bộ thất bại
    │
    ├── Cập nhật trạng thái → AwaitingExchange
    ├── Gắn ghi chú lỗi vào đơn hàng
    └── Gửi email đến ERP admin
            └── Fallback: locale hiện tại → locale cha → Tiếng Anh

Đồng Bộ Khách Hàng: Nửa Còn Lại

E-commerce → SAP:  Tạo khách hàng, xác minh địa chỉ, kiểm tra trùng VAT/PAN
SAP → E-commerce:  Đồng bộ địa chỉ (SAP là master), gán mã khách hàng
SAP → Identity:    Mã khách hàng SAP đẩy ngược về identity provider

Bài Học Rút Ra

  1. Cấu hình SAP theo từng locale, không phải global. Phải phản ánh điều này từ đầu.
  2. "Simulate trước khi tạo" cứu đơn hàng. Phát hiện vấn đề giá/thuế trước khi thu tiền.
  3. Không phải "error" SAP nào cũng là lỗi thật. Xây dựng catalog các non-fatal message.
  4. SOAP chưa chết, nó chỉ đang ẩn náu. Thiết kế transport như một layer có thể thay thế.
  5. Mapping payment phức tạp hơn bạn nghĩ. Khác nhau theo processor và thị trường.
  6. Đừng tin giá trị âm. Validate tổng thuế/phí vận chuyển từ SAP trước khi đưa vào checkout.

Kết Luận

Xây dựng tích hợp e-commerce với SAP về bản chất là bài toán dịch thuật. Cô lập tầng dịch thuật, validate chặt chẽ ở mọi ranh giới, và luôn giả định mọi lần gọi SAP đều có thể thất bại theo những cách bạn chưa nghĩ đến.

Bài viết mô tả các pattern kiến trúc từ các tích hợp SAP e-commerce thực tế. Tất cả các định danh và chi tiết triển khai đã được ẩn danh hóa.

#SAP#e-commerce#integration#SOAP#architecture#ERP
← Back to Articles
Hướng dẫn thực chiến của senior developer về xây dựng tầng tích hợp SAP real-time — bao gồm luồng dữ liệu, xử lý lỗi, và bài học xương máu từ môi trường production. - Ginbok