Kinh nghiệm lập trình website mã nguồn mở PHP

Magento Shipping Method qua dự án thực tế

Cùng tìm hiểu về Magento Shipping Method (giao vận, vận chuyển) qua một dự án thực tế – Giao vận ở Thái Lan.
Magento Shipping Method qua dự án thực tế

Giới thiệu

Mình đã có 1 bài viết nói về Hoạt động kinh doanh trong Magento 2, trong đó có đề cập tới Shipping, và cách hỗ trợ Shipping các tỉnh thành Việt Nam, nhận thấy đây là một chủ đề rất phổ biến trong các dự án eCommerce, nên mình viết bài này bổ sung kinh nghiệm thực tế qua 1 dự án thật, hy vọng sẽ mang lại cho các bạn nhiều điều bổ ích.

Trong bài này chúng ta sẽ tìm hiểu:

  • Đóng gói chức năng vào một extension trong Magento 2
  • Tích hợp tỉnh thành vào extension thông qua setup data
  • Phân tích nhu cầu doanh nghiệp và chọn phương án vận chuyển ở Thái Lan
  • Các nguyên tắc nằm lòng lập trình viên và tối ưu hóa

Magento Shipping Method New Extension

Đầu tiên là yêu cầu của dự án:
Tích hợp chức năng Shipping trong và ngoài nước Thái Lan.
– Nếu các sản phẩm là nệm thì miễn phí vận chuyển (trang web này bán nệm gối, mùng màn như Kim Đan)
– Đặc biệt nếu giao nệm tới các tỉnh có phiến quân hồi giáo IS thì sẽ không miện phí (Ặc! Thái Lan có IS rồi cơ à? Ai đi du lịch nhớ bảo trọng nha)
– Các sản phẩm khác thì mức giá vận chuyển như sau: trên 1000 Bath miễn phí, dưới 1000 Bath thì phí 100 Bath.
– Hỗ trợ ngoài nước với thang giá riêng.

Với yêu cầu như vậy, phân tích sơ sơ chúng ta sẽ phải làm các công việc: thêm các tỉnh thành cho Thái Lan, tùy biến shipping theo sản phẩm, thêm danh sách các tỉnh có IS…
Vậy là cần 1 extension, đầu tiên mình lên Market Place xem đã có sẵn cái nào chưa.
Chúng ta không nên phí thời gian và công sức viết lại những thứ đã có (mà tốt). Nếu hên ta sẽ có 1 extension miễn phí về đọc – học – tùy biến theo ý thích. Nếu bình thường sẽ có extension xịn thu phí, ta sẽ ngắm và đoán cách thức hoạt động thông qua hướng dẫn sử dụng (có ích lắm để ta tự viết 1 cái dựa trên ý tưởng). Nếu xui ta chả có extension nào. Shipping tại Thái Lan rơi vào trường hợp thứ 3 (Haha).
Không còn cách nào khác, ta phải tự xây dựng 1 extension thôi.

Tích hợp các tỉnh thành Thái Lan

Để thêm tỉnh thành cho bất cứ quốc gia nào mà Magento chưa hỗ trợ, ta chỉ cần thêm dữ liệu vào 2 bảng: directory_country_regiondirectory_country_region_name.
Lấy Danh sách các tỉnh thành Thái Lan
Ta sẽ viết dưới dạng setup data của extension:

# \Nam\Shipping\Setup\InstallData::install
$data = [
            ['TH', 'BKK', 'Bangkok', 'Bangkok'],
            ['TH', 'ACR', 'Amnat Charoen', 'Amnat Charoen'],
            ['TH', 'ATG', 'Ang Thong', 'Ang Thong'],
            ['TH', 'BKN', 'Bueng Kan', 'Bueng Kan'],
...

$connection = $setup->getConnection();
$regionTable = $setup->getTable('directory_country_region');
$regionNameTable = $setup->getTable('directory_country_region_name');

foreach ($data as $row) {
    $bind = [
        'country_id' => $row[0],
        'code' => $row[1],
        'default_name' => $row[2]
    ];
    $connection->insertOnDuplicate($regionTable, $bind);
    $regionId = $connection->lastInsertId($regionTable);

    $bind = [
        'locale' => 'en_US',
        'region_id' => $regionId,
        'name' => $row[2]
    ];
    $connection->insertOnDuplicate($regionNameTable, $bind);

    $bind = [
        'locale' => 'th_TH',
        'region_id' => $regionId,
        'name' => $row[3]
    ];
    $connection->insertOnDuplicate($regionNameTable, $bind);
}

Chúng ta vừa đưa $regionTable vào bảng region (country_id = TH thì khi chọn country = Thái, selectbox tỉnh thành sẽ tự động load dữ liệu ta đang thêm ở đây), còn bảng region_name là tên dịch của mỗi region theo ngôn ngữ mà website sử dụng, bên trên là 2 ngôn ngữ en_USth_TH

Sau khi insert region data và cài đặt extension vào hệ thống, lúc này khi checkout, chọn country Thái Lan, sẽ xuất hiện select list tỉnh thành (region, province) cho người dụng chọn. Vậy là ta đã tích hợp region thành công.


Tùy biến Magento Shipping Method Tablerates

Tiếp theo ta sẽ phân tích công đoạn khó nhất:

– Nếu các sản phẩm là nệm thì miễn phí vận chuyển
– Đặc biệt nếu giao nệm tới các tỉnh có phiến quân hồi giáo IS thì sẽ không miện phí.
– Các sản phẩm khác thì mức giá vận chuyển như sau: > 1000 Bath => miễn phí, < 1000 Bath => phí 100 Bath.

Ý tưởng:
Với yêu cầu đặc biệt này thì Magento không thể nào có chức năng giống y chang, tuy nhiên mục số 3 rất giống với Tablerates, vậy nên ta sẽ dựa trên Tablerate và thêm thắt chút logic đặc biệt, sẽ đơn giản hơn bạn tự viết hẳn 1 magento shipping method riêng.
Thực hiện:
Bật shipping Tablerates của Magento lên, cấu hình như sau:

– Ứng với trong nước Thái Lan thì ta có thang giá 0-1000 phí ship = 100, trên 1000 miễn phí
– Dấu * là với mọi nước còn lại (nước ngoài)

* Tại sao country code bước 1 trong setup data thì 2 ký tự TH, còn bây giờ là 3 THA?
2 hay 3 thì cũng là chuẩn ISO Code theo quốc tế. Tuy nhiên ông nội Magento cũng khá ngáo đá khi làm nhiều chuẩn gây rối cho mọi người, thiếu sự nhất quán, cũng may trong tài liệu Magento cũng nói rõ là xài chuẩn nào nên cũng đỡ.

Cấu hình xong Tablerates, ra Front-end làm vài phát checkout xem nó ăn chưa nhé, tiếp đến trước khi tùy biến ta phải hiểu rõ cơ chế hoạt động của Magento shipping method Tablerates.
Hàm xử lý chính của Tablerates là: \Magento\OfflineShipping\Model\Carrier\Tablerate::collectRates

if ($request->getFreeShipping() === true || $request->getPackageQty() == $freeQty) {
    $shippingPrice = 0;
} else {
    $shippingPrice = $this->getFinalPriceWithHandlingFee($rate['price']);
}
$method->setPrice($shippingPrice);

Ta chú ý đoạn code trên, $request là 1 biến đầu vào, nó chứa dữ liệu của giỏ hàng, địa chỉ shipping, đồng thời quyết định luôn giá của magento shipping method tablerates, vậy là chỉ cần ta lấy được $request, sau đó kiểm tra điều kiện sản phẩm trong danh mục nệm, cũng như địa chỉ shipping có trong black-list hay không, nếu thỏa ta setFreeShipping(true) là xong.

Để can thiệp vào hàm trong core Magento và điều khiển tham số đầu vào (input) ta dùng plugin-before

public function beforeCollectRates(
    \Magento\OfflineShipping\Model\Carrier\Tablerate $subject,
    RateRequest $request
) {
    if (!$subject->getConfigFlag('active')) {
        return false;
    }

    // do not free ship for black-list regions
    /*
    check if (in_array($request->getDestRegionId(), $blackListIds))
    return [$request];
    */

    // only free ship for products in special category
    // support only 1 category - should improve to support multiple categories
    if ($request->getAllItems()) {
        $categoryFreeShipId = $this->getConfigData('free_category');
        $isTheSameFreeCategory = true;
        foreach ($request->getAllItems() as $item) {
            if (!in_array($categoryFreeShipId, $item->getProduct()->getCategoryIds())) {
                $isTheSameFreeCategory = false;
                break;
            }
        }
        if ($isTheSameFreeCategory) {
            $request->setFreeShipping(true);
        }
    }
    return [$request];
}

Mình giả định rằng các bạn đã hiểu cơ chế hoạt động và cách sử dụng Plugin trong Magento
Mình chỉ đi vào logic code, như các bạn thấy, ta đã có 2 thông tin quan trọng nhất là getDestRegionId()getCategoryIds() trong $request nên đáp ứng được yêu cầu của bài toán shipping.
$blackListIds là danh sách các tỉnh thành sẽ không được free ship (IS), và được chọn thông qua admin dashboard.

Trong Magento Dashboard Admin, để hiển thị multiple selectbox danh sách quốc gia ta dùng source_model: Magento\Directory\Model\Config\Source\Country
Danh sách các tỉnh thành trong mỗi quốc gia: Magento\Directory\Model\Config\Source\Allregion

Ta có thể dùng cấu hình có sẵn để hiển thị toàn bộ tỉnh thành trong mỗi quốc gia:
etc/adminhtml/system.xml

<field id="specificcountry" translate="label" type="multiselect" sortOrder="91" showInDefault="1" showInWebsite="1" showInStore="0">
    <label>All Region</label>
    <source_model>Magento\Directory\Model\Config\Source\Allregion</source_model>
    <can_be_empty>1</can_be_empty>
</field>

Tuy nhiên ở đây ta chỉ cần tỉnh thành của Thái Lan mà thôi, nên phải tạo ra 1 class riêng để bỏ vào source_model. Class mới sẽ kế thừa từ class Allregion, mục đích là sử dụng lại biến của class cha $this->_regionCollectionFactory. Đặc biệt ta còn xem được code trong class cha – nó sẽ load danh sách country, load danh sách region, rồi đưa vào mảng 2 chiều với khóa là country_id và region_id. Ta chỉ cần tùy biến lại để load ra danh sách region thuộc Thái và đưa vào mảng 1 chiều là đủ:

public function toOptionArray($isMultiselect = false)
{
    if (!$this->_options) {
        $this->_options = [];
        $regionsCollection = $this->_regionCollectionFactory->create()->addFieldToFilter('country_id', 'TH');
        foreach ($regionsCollection as $region) {
            $this->_options[] = ['label' => $region->getDefaultName(), 'value' => $region->getId()];
        }
    }
    $options = $this->_options;
    if (!$isMultiselect) {
        array_unshift($options, ['value' => '', 'label' => '']);
    }
    return $options;
}

Hàm này rõ ràng tối ưu hóa hơn Allregion và vẫn đáp ứng được nhu cầu sử dụng, đáp ứng được nguyên tắc Keep it simple.
Bước tiếp theo khá đơn giản khi ta đưa category_id (danh mục nệm) vào cấu hình admin và load ra trong phần điều kiện.

Tổng kết

Ta vừa tích hợp các tỉnh thành Thái Lan và nâng cấp Tablerates shipping method để đáp ứng nhu cầu thực tế của khách hàng.
Hiểu được cơ chế region/country thì bạn có thể tích hợp cho bất cứ quốc gia nào mà mặc định Magento chưa có.
Tuy nhiên bài toán Magento Shipping Method vẫn còn nhiều điều phức tạp, nhất là các trang Thương mại điện tử có quy mô rộng khắp thế giới. Hy vọng sẽ gặp lại các bạn trong 1 dự án hay ho tiếp theo.

Magento Shipping Method qua dự án thực tế
Đánh giá bài viết

Gửi phản hồi

Your email address will not be published. Required fields are marked *