import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpServer; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.security.MessageDigest; import java.time.Instant; import java.util.Arrays; import java.util.HashSet; import java.util.Locale; import java.util.Set; import java.util.UUID; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; /** ABCPAY Java 8 示例:发送六类请求,也可接收两类回调。 */ public class AbcpayExample { private static final Set PATHS = new HashSet<>(Arrays.asList( "/abcpay/collection/create", "/abcpay/payout/create", "/abcpay/wallet/balance", "/abcpay/upi/query", "/abcpay/utr/query", "/abcpay/order/query")); private static String required(String name) { String value = System.getenv(name); if (value == null || value.isEmpty()) throw new IllegalArgumentException("请设置环境变量 " + name); return value; } private static byte[] readAll(InputStream input) throws Exception { ByteArrayOutputStream output = new ByteArrayOutputStream(); byte[] buffer = new byte[4096]; int count; while ((count = input.read(buffer)) != -1) output.write(buffer, 0, count); return output.toByteArray(); } private static String hex(byte[] bytes) { char[] alphabet = "0123456789abcdef".toCharArray(); char[] result = new char[bytes.length * 2]; for (int i = 0; i < bytes.length; i++) { result[i * 2] = alphabet[(bytes[i] >>> 4) & 15]; result[i * 2 + 1] = alphabet[bytes[i] & 15]; } return new String(result); } private static String sign(String key, String method, String path, String timestamp, String nonce, byte[] body) throws Exception { String digest = hex(MessageDigest.getInstance("SHA-256").digest(body)); String source = "ABCPAY-OPEN-1\n" + method.toUpperCase(Locale.ROOT) + "\n" + path + "\n" + timestamp + "\n" + nonce + "\n" + digest; Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); return hex(mac.doFinal(source.getBytes(StandardCharsets.UTF_8))); } private static void send(String[] args) throws Exception { String path = args.length == 0 ? "/abcpay/wallet/balance" : args[0]; if (!PATHS.contains(path)) throw new IllegalArgumentException("接口路径不在示例范围内"); byte[] body = args.length < 2 ? "{}".getBytes(StandardCharsets.UTF_8) : Files.readAllBytes(Paths.get(args[1])); String base = System.getenv("ABCPAY_BASE_URL"); if (base == null || base.isEmpty()) base = "https://abc.yamipay.cc"; String timestamp = Long.toString(Instant.now().getEpochSecond()); String nonce = UUID.randomUUID().toString().replace("-", ""); URL url = new URL(base.replaceAll("/+$", "") + path); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("POST"); connection.setConnectTimeout(10000); connection.setReadTimeout(20000); connection.setDoOutput(true); connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); connection.setRequestProperty("Abcpay-Merchant", required("ABCPAY_MERCHANT")); connection.setRequestProperty("Abcpay-Timestamp", timestamp); connection.setRequestProperty("Abcpay-Nonce", nonce); connection.setRequestProperty("Abcpay-Signature", sign(required("ABCPAY_API_KEY"), "POST", path, timestamp, nonce, body)); try (OutputStream output = connection.getOutputStream()) { output.write(body); } int status = connection.getResponseCode(); InputStream response = status < 400 ? connection.getInputStream() : connection.getErrorStream(); byte[] responseBody = response == null ? new byte[0] : readAll(response); if (response != null) response.close(); System.out.println("HTTP " + status); System.out.println(new String(responseBody, StandardCharsets.UTF_8)); connection.disconnect(); } private static void reply(HttpExchange exchange, int status, String text) throws Exception { byte[] body = text.getBytes(StandardCharsets.UTF_8); exchange.getResponseHeaders().set("Content-Type", "text/plain; charset=UTF-8"); exchange.sendResponseHeaders(status, body.length); try (OutputStream output = exchange.getResponseBody()) { output.write(body); } } private static void callback(HttpExchange exchange) { try { String path = exchange.getRequestURI().getRawPath(); if (!"POST".equals(exchange.getRequestMethod()) || !("/notify/collection".equals(path) || "/notify/payout".equals(path))) { reply(exchange, 405, "请求方式或路径不正确"); return; } byte[] body = readAll(exchange.getRequestBody()); String merchant = exchange.getRequestHeaders().getFirst("Abcpay-Merchant"); String timestamp = exchange.getRequestHeaders().getFirst("Abcpay-Timestamp"); String nonce = exchange.getRequestHeaders().getFirst("Abcpay-Nonce"); String signature = exchange.getRequestHeaders().getFirst("Abcpay-Signature"); if (!required("ABCPAY_MERCHANT").equals(merchant) || timestamp == null || !timestamp.matches("[0-9]{1,15}") || Math.abs(Instant.now().getEpochSecond() - Long.parseLong(timestamp)) > 300 || nonce == null || !nonce.matches("[A-Za-z0-9_-]{16,64}") || signature == null || !signature.matches("(?i)[0-9a-f]{64}")) { reply(exchange, 401, "回调鉴权失败"); return; } String expected = sign(required("ABCPAY_API_KEY"), "POST", path, timestamp, nonce, body); if (!MessageDigest.isEqual(expected.getBytes(StandardCharsets.US_ASCII), signature.toLowerCase(Locale.ROOT).getBytes(StandardCharsets.US_ASCII))) { reply(exchange, 401, "回调签名错误"); return; } // 演示接收:正式业务应先持久化 noticeId 并完成幂等处理,再返回 ACK。 System.out.println(new String(body, StandardCharsets.UTF_8)); reply(exchange, 200, "ACK"); } catch (Exception error) { try { reply(exchange, 500, "回调处理失败"); } catch (Exception ignored) { } } } private static void serve() throws Exception { int port = Integer.parseInt(System.getenv().getOrDefault("ABCPAY_CALLBACK_PORT", "8080")); HttpServer server = HttpServer.create(new InetSocketAddress(port), 0); server.createContext("/notify/collection", AbcpayExample::callback); server.createContext("/notify/payout", AbcpayExample::callback); server.start(); System.out.println("回调监听端口:" + port); } public static void main(String[] args) throws Exception { if (args.length > 0 && "serve".equals(args[0])) serve(); else send(args); } }