// 本 Demo 演示签名与下单。sign = HMAC-SHA256(参数按字段名升序拼成 k=v 用 & 连接、末尾追加 "&secret=密钥") 的 hex 值。嵌套对象（extra 等）按 key 升序紧凑 JSON 序列化后参与签名。请把占位符替换为商户实际凭据。
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.TreeMap;
import java.util.HexFormat;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

String baseUrl = "<BASE_URL>";
String merchantNo = "<YOUR_MERCHANT_NO>";
String apiKey = "<YOUR_API_KEY>";
String apiSecret = "<API_SECRET>";

Map<String, Object> payload = new TreeMap<>();
payload.put("merchant_no", merchantNo);
payload.put("api_key", apiKey);
payload.put("timestamp", System.currentTimeMillis() / 1000);
payload.put("nonce", "random-xyz");
payload.put("out_order_no", "202501010001");
payload.put("amount", 10000);
payload.put("currency", "PHP");
payload.put("pay_method", "gcash");
payload.put("country", "PH");
payload.put("notify_url", "https://merchant.example.com/api/notify/pay");
Map<String, Object> extra = new TreeMap<>();
Map<String, Object> customer = new TreeMap<>();
customer.put("first_name", "<FIRST_NAME>");
customer.put("last_name", "<LAST_NAME>");
customer.put("email", "<EMAIL>");
customer.put("phone", "<PHONE>");
extra.put("customer", customer);
payload.put("extra", extra);

// signMapper：key 升序序列化嵌套对象，与服务端稳定签名口径一致
ObjectMapper signMapper = new ObjectMapper();
signMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
StringBuilder raw = new StringBuilder();
for (Map.Entry<String, Object> e : payload.entrySet()) {
  if ("sign".equals(e.getKey()) || e.getValue() == null) { continue; }
  Object val = e.getValue();
  String strVal = (val instanceof Map || val instanceof java.util.Collection)
      ? signMapper.writeValueAsString(val) : String.valueOf(val);
  raw.append(e.getKey()).append("=").append(strVal).append("&");
}
raw.append("secret=").append(apiSecret);

Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
String sign = HexFormat.of().formatHex(mac.doFinal(raw.toString().getBytes(StandardCharsets.UTF_8)));
payload.put("sign", sign);

ObjectMapper mapper = new ObjectMapper();
String body = mapper.writeValueAsString(payload);

HttpRequest req = HttpRequest.newBuilder()
  .uri(URI.create(baseUrl + "/merchant/pay/create"))
  .header("Content-Type", "application/json")
  .POST(HttpRequest.BodyPublishers.ofString(body))
  .build();
HttpResponse<String> resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(resp.body());