import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
public class PaymentDemo {
// === 模擬:付款方式 & 狀態 ===
enum PaymentMethod { CREDIT_CARD, ATM, CVS, BARCODE }
enum PaymentStatus { INIT, PAID, FAILED, REFUND }
// === 訂單物件 ===
static class Order {
private final String orderId;
private int amount;
private PaymentMethod method;
private PaymentStatus status;
private final LocalDateTime createdAt;
public Order(String orderId, int amount, PaymentMethod method, PaymentStatus status) {
this.orderId = orderId;
this.amount = amount;
this.method = method;
this.status = status;
this.createdAt = LocalDateTime.now();
}
public String getOrderId() { return orderId; }
public int getAmount() { return amount; }
public PaymentMethod getMethod() { return method; }
public PaymentStatus getStatus() { return status; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setStatus(PaymentStatus status) { this.status = status; }
public void setAmount(int amount) { this.amount = amount; }
public void setMethod(PaymentMethod method) { this.method = method; }
// 以 orderId 當唯一識別,利於在 Set / Map 判斷唯一性
@Override public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Order)) return false;
Order order = (Order) o;
return Objects.equals(orderId, order.orderId);
}
@Override public int hashCode() { return Objects.hash(orderId); }
@Override public String toString() {
return "Order{" +
"orderId='" + orderId + '\'' +
", amount=" + amount +
", method=" + method +
", status=" + status +
", createdAt=" + createdAt +
'}';
}
}
// === 通用:依 key 升冪排序 Map 並轉 querystring,再做 SHA-256 (示意用,非特定廠商實作) ===
static String signParams(Map<String, String> params, String secret) {
// 1) TreeMap:照 key 升冪(常見於金流簽章)
Map<String, String> sorted = new TreeMap<>(params);
// 2) 串接為 key=value&key=value...
String query = sorted.entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("&"));
// 3) 加鹽/密鑰(依不同金流文件規範處理,這裡示範在前後加上 secret)
String toHash = secret + query + secret;
// 4) SHA-256
return sha256Hex(toHash).toUpperCase();
}
static String sha256Hex(String s) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] out = md.digest(s.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder(out.length * 2);
for (byte b : out) {
sb.append(String.format("%02x", b));
}
return sb.toString();
} catch (Exception e) {
throw new RuntimeException("Hash error", e);
}
}
public static void main(String[] args) {
// ===== 1) Set:維護唯一的支援付款方式,並建立不可變視圖 =====
Set<PaymentMethod> supported = new HashSet<>();
supported.add(PaymentMethod.CREDIT_CARD);
supported.add(PaymentMethod.ATM);
supported.add(PaymentMethod.CVS);
supported.add(PaymentMethod.CREDIT_CARD); // 重複,不會加入
Set<PaymentMethod> supportedReadonly = Collections.unmodifiableSet(supported);
System.out.println("支援付款方式(唯一路徑 + 不可變):" + supportedReadonly);
// ===== 2) List:保存訂單建立順序,後續可以排序/過濾 =====
List<Order> orders = new ArrayList<>();
orders.add(new Order("A20250822001", 1500, PaymentMethod.CREDIT_CARD, PaymentStatus.INIT));
orders.add(new Order("A20250822002", 2999, PaymentMethod.ATM, PaymentStatus.PAID));
orders.add(new Order("A20250822003", 800, PaymentMethod.CVS, PaymentStatus.FAILED));
orders.add(new Order("A20250822004", 5000, PaymentMethod.CREDIT_CARD, PaymentStatus.PAID));
System.out.println("\n原始建立順序:");
orders.forEach(System.out::println);
// Collections.sort + Comparator:依金額降冪排序
orders.sort(Comparator.comparingInt(Order::getAmount).reversed());
System.out.println("\n依金額(大→小)排序:");
orders.forEach(System.out::println);
// max/min:找最大/最小金額訂單
Order maxOrder = Collections.max(orders, Comparator.comparingInt(Order::getAmount));
Order minOrder = Collections.min(orders, Comparator.comparingInt(Order::getAmount));
System.out.println("\n最高金額訂單:" + maxOrder);
System.out.println("最低金額訂單:" + minOrder);
// ===== 3) Map:以訂單編號快速查詢/更新 =====
Map<String, Order> orderIndex = new HashMap<>();
for (Order o : orders) {
orderIndex.put(o.getOrderId(), o);
}
// 模擬:金流回調把某訂單改為 PAID
String callbackOrderId = "A20250822001";
Order toUpdate = orderIndex.get(callbackOrderId);
if (toUpdate != null) {
toUpdate.setStatus(PaymentStatus.PAID);
System.out.println("\n回調更新狀態 → PAID:" + toUpdate);
}
// 模擬:查詢不存在訂單
Order notFound = orderIndex.getOrDefault("NO_SUCH_ORDER", null);
System.out.println("查無訂單結果:" + notFound);
// ===== 4) 典型彙總:用 Map.merge() 按付款方式累加營收 =====
Map<PaymentMethod, Integer> revenueByMethod = new EnumMap<>(PaymentMethod.class);
for (Order o : orders) {
if (o.getStatus() == PaymentStatus.PAID) {
revenueByMethod.merge(o.getMethod(), o.getAmount(), Integer::sum);
}
}
System.out.println("\n各付款方式營收彙總(僅 PAID): " + revenueByMethod);
// ===== 5) Collection 介面:以最上層型別做共通處理(寫法更抽象) =====
Collection<Order> orderView = orders; // List 是 Collection 的子介面
long paidCount = orderView.stream().filter(o -> o.getStatus() == PaymentStatus.PAID).count();
System.out.println("\nPAID 訂單筆數(Collection 流式計算):" + paidCount);
// ===== 6) 利用 Set 去重:合併多來源的「回調事件」,避免重複處理 =====
List<String> gatewayCallbacks = List.of(
"A20250822002", "A20250822002", "A20250822003", "A20250822004"
);
Set<String> uniqueCallbackIds = new HashSet<>(gatewayCallbacks); // 去重
System.out.println("\n回調訂單(原始/去重):" + gatewayCallbacks + " / " + uniqueCallbackIds);
// ===== 7) 參數簽章(常見:依字典序排序 → 串接 → 雜湊)=====
Map<String, String> payParams = new HashMap<>();
payParams.put("MerchantID", "2000132");
payParams.put("MerchantTradeNo", "A20250822001");
payParams.put("MerchantTradeDate", "2025/08/22 20:45:00");
payParams.put("PaymentType", "aio");
payParams.put("TotalAmount", "1500");
payParams.put("TradeDesc", "測試訂單");
payParams.put("ItemName", "測試商品");
// ... 實務上請依廠商文件補齊必要欄位
String secret = "YOUR_SECRET_KEY"; // 示意:請改成你的金流密鑰
String signature = signParams(payParams, secret);
System.out.println("\n簽章結果(示意):" + signature);
// ===== 8) Collections 的其他實用技巧 =====
// 8-1) 建立不可變空集合:避免 null(回傳型別友善)
List<Order> emptySafeList = Collections.emptyList();
System.out.println("\n安全空清單(避免 null):" + emptySafeList);
// 8-2) 建立只讀快照:避免呼叫端誤改
List<Order> snapshot = Collections.unmodifiableList(new ArrayList<>(orders));
System.out.println("只讀快照大小:" + snapshot.size());
// snapshot.add(...) // 這行若解除註解會拋 UnsupportedOperationException
// 8-3) 同步化(若多執行緒下要保護 List)
List<Order> threadSafeList = Collections.synchronizedList(new ArrayList<>(orders));
synchronized (threadSafeList) { // 迭代時需額外同步
// do something
}
// 8-4) 頻率統計(例如失敗原因/錯誤碼),這裡示範用 Collections.frequency
List<PaymentStatus> statusList = orders.stream().map(Order::getStatus).toList();
int failedCount = Collections.frequency(statusList, PaymentStatus.FAILED);
System.out.println("FAILED 筆數(Collections.frequency):" + failedCount);
}
}