package com.wyrs.admin.service.config; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.wyrs.admin.config.WyrsConfigProperties; import com.wyrs.admin.entity.MembershipPrice; import com.wyrs.admin.util.PackageNameContext; import java.math.BigDecimal; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import org.springframework.stereotype.Service; @Service public class PricesConfigService extends AbstractEntityListConfigService { private static final TypeReference> TYPE = new TypeReference>() {}; public PricesConfigService(WyrsConfigProperties config, ObjectMapper objectMapper) { super(config.resolveFile("prices.json"), objectMapper, TYPE, MembershipPrice::getId, MembershipPrice::getAppCode); } public List findByAppCode(String appCode) { // Admin: all packages under appCode return this.findByAppCodeOrderByPriceAsc(appCode, false, null); } public List findByAppCodeAndEnabledTrueOrderByPriceAsc(String appCode) { // Client: filter by X-Wyrs-Package / packageName return this.findByAppCodeOrderByPriceAsc(appCode, true, PackageNameContext.get()); } private List findByAppCodeOrderByPriceAsc(String appCode, boolean enabledOnly, String packageName) { Object object = this.lock; synchronized (object) { List base = this.store.getItems().stream() .filter(p -> appCode.equals(p.getAppCode())) .filter(p -> !enabledOnly || Boolean.TRUE.equals(p.getEnabled())) .sorted(Comparator.comparing(MembershipPrice::getPrice, Comparator.nullsLast(BigDecimal::compareTo))) .toList(); return filterByPackage(base, packageName); } } /** * Empty packageName: return all. * Non-empty: prefer exact package match; else fall back to blank packageName defaults. */ public static List filterByPackage(List items, String packageName) { if (packageName == null || packageName.isBlank()) { return items; } String want = packageName.trim().toLowerCase(Locale.ROOT); List exact = items.stream() .filter(p -> { String pkg = p.getPackageName(); return pkg != null && !pkg.isBlank() && pkg.trim().toLowerCase(Locale.ROOT).equals(want); }) .toList(); if (!exact.isEmpty()) { return exact; } return items.stream() .filter(p -> { String pkg = p.getPackageName(); return pkg == null || pkg.isBlank(); }) .toList(); } public MembershipPrice save(MembershipPrice price) { if (price != null) { if (price.getPackageName() == null) { price.setPackageName(""); } if (price.getBillingType() == null || price.getBillingType().isBlank()) { Integer days = price.getDurationDays(); price.setBillingType(days != null && days > 0 ? "subscription" : "one_time"); } else { String bt = price.getBillingType().trim().toLowerCase(Locale.ROOT); if ("sub".equals(bt) || "renew".equals(bt) || "auto".equals(bt)) { bt = "subscription"; } if ("once".equals(bt) || "onetime".equals(bt) || "iap".equals(bt)) { bt = "one_time"; } price.setBillingType("subscription".equals(bt) ? "subscription" : "one_time"); } if (price.getChannelProductCodes() == null) { price.setChannelProductCodes(new LinkedHashMap<>()); } } return this.save(price, MembershipPrice::setId); } }