Oms-idempiere
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
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.
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
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