Oms-idempiere

From iDempiere en

Development (Java Custom Code)-OrderGetProcess

In the Development role, we implemented a custom Java Process that integrates with Cyberbiz to fetch and convert online orders into iDempiere sales orders (MOrder and MOrderLine).

Below is the Java code example used in this integration:

via :https://api-doc.cyberbiz.co/v1/api_document#!/orders/getV1Orders

package tw.wp.process;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.sql.Timestamp;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Locale;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.compiere.model.MBPartner;
import org.compiere.model.MOrder;
import org.compiere.model.MOrderLine;
import org.compiere.process.DocAction;
import org.compiere.process.ProcessInfoParameter;
import org.compiere.process.SvrProcess;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.compiere.util.TimeUtil;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.Gson;

import tw.wp.order.DTO.CyberbizOrder;
import tw.wp.order.DTO.ParameterDto;

public class OrderGetProcess extends SvrProcess {
    private ParameterDto parameters = new ParameterDto();

    @Override
    protected void prepare() {
        for (ProcessInfoParameter param : getParameter()) {
            switch (param.getParameterName()) {
                case "channel" -> parameters.channel = param.getParameterAsString();
                case "platform" -> parameters.platform = param.getParameterAsString();
                case "token1" -> parameters.token1 = param.getParameterAsString();
                case "token2" -> parameters.token2 = param.getParameterAsString();
                case "token3" -> parameters.token3 = param.getParameterAsString();
                case "token4" -> parameters.token4 = param.getParameterAsString();
                case "token5" -> parameters.token5 = param.getParameterAsString();
                case "channel_sn" -> parameters.channel_sn = param.getParameterAsString();
            }
        }
    }

    @Override
    protected String doIt() throws Exception {
        int AD_Org_ID = Env.getAD_Org_ID(getCtx());

        if ("cyberbiz".equals(parameters.platform)) {
            List<CyberbizOrder> cyberbizOrders = getdata(parameters);

            for (CyberbizOrder cyberbizOrder : cyberbizOrders) {
                MOrder order = createOrder(AD_Org_ID, cyberbizOrder.order_number);

                for (CyberbizOrder.LineItem lineItem : cyberbizOrder.line_items) {
                    int productId = getProductIdBySKU(lineItem.sku);
                    if (productId <= 0) {
                        log.warning("SKU not found: " + lineItem.sku);
                        continue;
                    }
                    createOrderLine(order, lineItem, productId);
                }
            }
        } else if ("cyberbizV2".equals(parameters.platform)) {
            List<CyberbizOrder> cyberbizOrders = cyberbizV2Getdata(parameters);

            for (CyberbizOrder cyberbizOrder : cyberbizOrders) {
                MOrder order = createOrder(AD_Org_ID, cyberbizOrder.order_number);

                for (CyberbizOrder.LineItem lineItem : cyberbizOrder.line_items) {
                    int productId = getProductIdBySKU(lineItem.sku);
                    if (productId <= 0) {
                        log.warning("SKU not found: " + lineItem.sku);
                        continue;
                    }
                    createOrderLine(order, lineItem, productId);
                }
            }
        }

        return "Done";
    }

    private MOrder createOrder(int AD_Org_ID, String documentNo) {
        MOrder order = new MOrder(getCtx(), 0, get_TrxName());
        order.setBPartner(MBPartner.get(Env.getCtx(), 118));
        order.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard);
        order.setDeliveryRule(MOrder.DELIVERYRULE_CompleteOrder);
        order.setDocStatus(DocAction.STATUS_Drafted);
        order.setDocAction(DocAction.ACTION_Complete);
        order.setDatePromised(TimeUtil.getDay(System.currentTimeMillis()));
        order.setDocumentNo(documentNo);
        order.setDescription("fromCyberbiz");
        order.setAD_Org_ID(AD_Org_ID);
        order.setM_Warehouse_ID(103);
        order.saveEx();
        return order;
    }

    private void createOrderLine(MOrder order, CyberbizOrder.LineItem lineItem, int productId) {
        MOrderLine orderLine = new MOrderLine(getCtx(), 0, get_TrxName());
        orderLine.setC_Order_ID(order.getC_Order_ID());
        orderLine.setM_Product_ID(productId, true);
        orderLine.setQty(new BigDecimal(lineItem.quantity));
        orderLine.setPrice(new BigDecimal(lineItem.price));
        orderLine.setDescription(lineItem.title);
        orderLine.setM_Warehouse_ID(order.getM_Warehouse_ID());
        orderLine.setAD_Org_ID(order.getAD_Org_ID());
        orderLine.saveEx();
    }

    private int getProductIdBySKU(String sku) {
        String sql = "SELECT M_Product_ID FROM M_Product WHERE Value = ?";
        return DB.getSQLValueEx(null, sql, sku);
    }

    private List<CyberbizOrder> getdata(ParameterDto parameters) throws NoSuchAlgorithmException, InvalidKeyException, IOException {
        String uri = "/v1/orders?page=1&per_page=20&offset=0";
        String url = "https://api.cyberbiz.co" + uri;
        String httpMethod = "GET";
        String requestLine = httpMethod + " " + uri + " HTTP/1.1";

        String xDate = ZonedDateTime.now(ZoneOffset.UTC)
                .format(DateTimeFormatter.RFC_1123_DATE_TIME.withLocale(Locale.ENGLISH));

        String apiKey = parameters.token1;
        String apiSecret = parameters.token2;

        String signData = "x-date: " + xDate + "\n" + requestLine;
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKey = new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        mac.init(secretKey);
        String signature = Base64.getEncoder().encodeToString(mac.doFinal(signData.getBytes(StandardCharsets.UTF_8)));

        String authorization = String.format(
                "hmac username=\"%s\", algorithm=\"hmac-sha256\", headers=\"x-date request-line\", signature=\"%s\"",
                apiKey, signature);

        HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection();
        con.setRequestMethod(httpMethod);
        con.setRequestProperty("Accept", "application/json");
        con.setRequestProperty("X-Date", xDate);
        con.setRequestProperty("Authorization", authorization);

        try (BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8))) {
            StringBuilder response = new StringBuilder();
            String line;
            while ((line = in.readLine()) != null) response.append(line);

            JsonArray jsonArray = JsonParser.parseString(response.toString()).getAsJsonArray();
            Gson gson = new Gson();
            List<CyberbizOrder> orders = new ArrayList<>();
            for (JsonElement element : jsonArray) {
                orders.add(gson.fromJson(element.getAsJsonObject(), CyberbizOrder.class));
            }
            return orders;
        }
    }

    private List<CyberbizOrder> cyberbizV2Getdata(ParameterDto parameters) throws IOException {
        String url = "https://app-store-api.cyberbiz.io/v1/orders?page=1&per_page=20&offset=0";
        HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection();
        con.setRequestMethod("GET");
        con.setRequestProperty("Accept", "application/json");
        con.setRequestProperty("Content-Type", "application/json");
        con.setRequestProperty("Authorization", "Bearer " + parameters.token1);

        try (BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8))) {
            StringBuilder response = new StringBuilder();
            String line;
            while ((line = in.readLine()) != null) response.append(line);

            JsonArray jsonArray = JsonParser.parseString(response.toString()).getAsJsonArray();
            Gson gson = new Gson();
            List<CyberbizOrder> orders = new ArrayList<>();
            for (JsonElement element : jsonArray) {
                orders.add(gson.fromJson(element.getAsJsonObject(), CyberbizOrder.class));
            }
            return orders;
        }
    }
}

Development (Java Custom Code)-RefreshTokenProcess

Cyberbiz V2 uses the OAuth2.0 authentication protocol, which requires periodic refreshing of the access_token using a refresh_token. In iDempiere, this can be automated through a custom SvrProcess executed by the AD_Scheduler. The implementation involves reading and updating scheduler parameters (AD_Scheduler_Para) for the tokens, and calling the Cyberbiz API with the required client_id and client_secret.

Note: This example assumes a single-tenant configuration. For multi-tenant systems, additional consideration is needed to store and retrieve the client credentials per tenant context.

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

import org.compiere.model.MSysConfig;
import org.compiere.model.Query;
import org.compiere.process.ProcessInfoParameter;
import org.compiere.process.SvrProcess;
import org.compiere.util.AdempiereUserError;
import org.compiere.util.DB;

import com.google.gson.Gson;

public class RefreshTokenProcess extends SvrProcess {
	private String target_scheduler;

	@Override
	protected void prepare() {
		for (ProcessInfoParameter para : getParameter()) {
			if ("target_scheduler".equals(para.getParameterName())) {
				target_scheduler = para.getParameterAsString();
			}
		}
	}

	@Override
	protected String doIt() throws Exception {
		if (target_scheduler == null || target_scheduler.isEmpty()) {
			throw new AdempiereUserError("Target Scheduler Name is required");
		}

		int targetSchedulerID = new Query(getCtx(), "AD_Scheduler", "Name=?", get_TrxName())
				.setParameters(target_scheduler).firstId();

		if (targetSchedulerID <= 0) {
			throw new AdempiereUserError("Scheduler not found: " + target_scheduler);
		}

		String refreshToken = getParameterDefault(targetSchedulerID, "token2");
		String refreshTokenUU = getParameterUU(targetSchedulerID, "token2");
		String accessTokenUU = getParameterUU(targetSchedulerID, "token1");
		String platform = getParameterDefault(targetSchedulerID, "platform");
		String channelSn = getParameterDefault(targetSchedulerID, "channel_sn");

		if ("cyberbizV2".equals(platform)) {
			String responseJson = refreshToken(channelSn, refreshToken);
			if (responseJson == null) {
				throw new AdempiereUserError("Refresh token response is null");
			}

			Gson gson = new Gson();
			Map<String, Object> responseMap = gson.fromJson(responseJson, Map.class);

			executeUpdate(String.valueOf(responseMap.get("access_token")), accessTokenUU);
			executeUpdate(String.valueOf(responseMap.get("refresh_token")), refreshTokenUU);
		}

		return "Scheduler '" + target_scheduler + "' updated.";
	}

	private String getParameterDefault(int AD_Scheduler_ID, String name) {
		String sql = """
			SELECT asp.parameterdefault
			FROM AD_Scheduler_Para asp
			INNER JOIN AD_Process_Para adp ON asp.ad_process_para_id = adp.ad_process_para_id
			WHERE asp.AD_Scheduler_ID = ? AND adp.name = ?
		""";
		return DB.getSQLValueStringEx(null, sql, AD_Scheduler_ID, name);
	}

	private String getParameterUU(int AD_Scheduler_ID, String name) {
		String sql = """
			SELECT asp.ad_scheduler_para_uu
			FROM AD_Scheduler_Para asp
			INNER JOIN AD_Process_Para adp ON asp.ad_process_para_id = adp.ad_process_para_id
			WHERE asp.AD_Scheduler_ID = ? AND adp.name = ?
		""";
		return DB.getSQLValueStringEx(null, sql, AD_Scheduler_ID, name);
	}

	private int executeUpdate(String value, String paraUU) {
		String sql = "UPDATE AD_Scheduler_Para SET parameterdefault = ? WHERE ad_scheduler_para_uu = ?";
		return DB.executeUpdate(sql, new Object[]{value, paraUU}, false, null);
	}

	private String refreshToken(String channelSn, String refreshToken) {
		String urlStr = "https://" + channelSn + "/admin/oauth/token";
		String clientId = MSysConfig.getValue("z_thirdly_cyberbiz_openapi_id", "", getAD_Client_ID());
		String clientSecret = MSysConfig.getValue("z_thirdly_cyberbiz_openapi_key", "", getAD_Client_ID());

		Map<String, String> params = new HashMap<>();
		params.put("grant_type", "refresh_token");
		params.put("refresh_token", refreshToken);
		params.put("client_id", clientId);
		params.put("client_secret", clientSecret);

		Gson gson = new Gson();
		String jsonInputString = gson.toJson(params);

		HttpURLConnection con = null;
		try {
			URL url = new URL(urlStr);
			con = (HttpURLConnection) url.openConnection();
			con.setRequestMethod("POST");
			con.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
			con.setDoOutput(true);

			try (OutputStream os = con.getOutputStream()) {
				os.write(jsonInputString.getBytes(StandardCharsets.UTF_8));
			}

			int responseCode = con.getResponseCode();
			BufferedReader br = new BufferedReader(new InputStreamReader(
					(responseCode >= 200 && responseCode < 300)
							? con.getInputStream()
							: con.getErrorStream(),
					StandardCharsets.UTF_8));

			StringBuilder responseBuilder = new StringBuilder();
			String line;
			while ((line = br.readLine()) != null) {
				responseBuilder.append(line.trim());
			}
			br.close();

			String responseJson = responseBuilder.toString();
			log.info("Response JSON: " + responseJson);
			return responseJson;

		} catch (Exception e) {
			log.severe("Exception when refreshing token: " + e.getMessage());
			return null;
		} finally {
			if (con != null) {
				con.disconnect();
			}
		}
	}
}

System Role – Register the Order get Process

Under the System role, we registered the new Java Process using the "Process & Report" window. Key configurations:

  • Classname: tw.wp.process.OrderGetProcess
  • Entity Type: User or Custom
  • Access Level: Client+Org
  • Name : OMS
  • Param: channel , platform , channel_sn , token1 , token2 , token3 , token4 , token5

We also assigned it to a menu item so it can be called manually or scheduled.

it's recommended to store third-party credentials like ID and SECRET in MSysConfig for centralized management

Process.png

System Role – Register the Refresh Token Process

Under the System role, we registered the new Java Process using the "Process & Report" window. Key configurations:

  • Classname: tw.wp.process.RefreshTokenProcess
  • Entity Type: User or Custom
  • Access Level: Client+Org
  • Name : OMS-refresh-token
  • Param: target_scheduler


Tenant Role – Create a Order get Scheduler

Switching to the Client role, we opened the "Schedule" window and created a scheduler for the process:

  • Process: Select the Cyberbiz order fetch process
  • Frequency: e.g., 5 mins
  • Active: Yes

This allows automatic fetching of Cyberbiz orders on a scheduled basis.

Set schedule process.png

cyberbiz sample :

channel : apidemo

platform : cyberbiz

token1 : apidemo

token2 : apidemo


Tenant Role – Create a Order get Scheduler(V2)

Switching to the Client role, we opened the "Schedule" window and created a scheduler for the process:

  • Process: Select the Cyberbiz order fetch process
  • Frequency: e.g., 5 mins
  • Active: Yes

This allows automatic fetching of Cyberbiz orders on a scheduled basis.

cyberbiz sample :

channel : apidemo

platform : cyberbizV2

channel_sn: ( cyberbiz shop DNS)

token1 : (get via api token)

token2 : (get via api refresh token)


Tenant Role – Create a Refresh Token Scheduler

Switching to the Client role, we opened the "Schedule" window and created a scheduler for the process:

  • Process: Select the Refresh Token fetch process
  • Frequency: e.g., Every hour
  • Active: Yes

This allows automatic fetching of Cyberbiz orders on a scheduled basis.

cyberbiz sample :

target_scheduler : cyberbizV2

Check DB

select * from  c_order  where description  = 'fromCyberbiz'


Token sample

ref:https://handbook.oneec.ai/scm/channels/channels-manager Token Parameters for Channels
Platform Channel Channel SN Token1 Token2 Token3 Token4 Token5
momo SCM master account SCM master account uni code Vendor ID SCM master password OTP last 3 digits
shopee StoreID Token refresh token
yahoo mall SCM master account KeyValue KeyIV SaltKey Token keyversion
etmall Supplier account Supplier account Supplier password APISecureKey (exclude VAT ID)
91app Shop serial number API Key
shopline Token refresh token
cyberbiz(via third party) Url Token refresh token
cyberbiz account password
shopify Url Token
easystore Url Token
pchome VendorId Key IV
coupang VendorId AccessKey SecretKey
rakuten Url Token
iOPEN mall Store ID Client ID Client Secret
friday Supplier ID or AI e-commerce ID Supplier API Key (DER format)
ruten API Key secretKey saltKey
books client ID client Secret maskPre maskPost IV key

This design exists because many merchants operate multiple stores on the same platform.

For example, a single Shopee seller may have three separate channels.

To accommodate this, the system is structured accordingly.

In the context of Taiwan, having up to five tokens (token1 to token5) is generally sufficient.

If your use case requires more, you can extend the configuration as needed.

MSysConfig Setting

If you apply to become a third-party authorized application for a platform, you will receive an official Client ID and Client Secret to authenticate your application. It is recommended to store this pair of credentials in MSYSCONFIG, as shown in the example below.

z_thirdly_cyberbiz_openapi_id
z_thirdly_cyberbiz_openapi_key


Notes

This integration currently supports Cyberbiz only as a sample.

More e-commerce platforms will be integrated in the future as part of the broader Order Management System (OMS).

  • Cyberbiz API v1 uses legacy HMAC authentication (API Key + Secret + Timestamp).
  • Cyberbiz API v2 uses modern OAuth2 authorization for third-party applications.

In this example, both versions (v1 and v2) are included to demonstrate different authentication mechanisms and to serve as a reference for future multi-platform integration.

For multi-tenant scenarios, additional configuration logic will be required (e.g., storing client ID and secret per tenant).

The best method is create a new table to save these token , otherwise it will have problem when change token


TODO

register ui ,redirect url , worker factory

api doc( open )

https://developer.91app.com/zh-tw/getting-started/getting-started/platform-introduction/

https://api-doc.cyberbiz.co/v1/api_document

https://cyberbiz.notion.site/OAuth-006d6e58ca8d4cd6b03ffc32556bd1b0

https://developers.easystore.co/docs/api/getting-started

https://open.shopee.com/documents/v2/v2.product.get_category?module=89&type=1

https://shopify.dev/docs/api/admin-graphql

https://open-api.docs.shoplineapp.com/docs/getting-started

Cookies help us deliver our services. By using our services, you agree to our use of cookies.