All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.redkalex.pay.OppoPayService Maven / Gradle / Ivy

There is a newer version: 2.7.7
Show newest version
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package org.redkalex.pay;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.logging.*;
import java.util.regex.Pattern;
import org.redkale.annotation.*;
import org.redkale.annotation.AutoLoad;
import org.redkale.annotation.Comment;
import org.redkale.convert.json.JsonConvert;
import org.redkale.inject.ResourceEvent;
import org.redkale.service.Local;
import org.redkale.util.*;
import static org.redkalex.pay.PayRetCodes.*;

/**
 * 详情见: https://redkale.org
 *
 * @author zhangjx
 */
@Local
@AutoLoad(false)
public final class OppoPayService extends AbstractPayService {

    protected static final String format = "%1$tY%1$tm%1$td%1$tH%1$tM%1$tS"; // yyyyMMddHHmmss

    protected static final Pattern PAYXML =
            Pattern.compile("<([^/>]+)>(.+)"); // "<([^/>]+)>"

    // 原始的配置
    protected Properties elementProps = new Properties();

    // 配置对象集合
    protected Map elements = new HashMap<>();

    @Resource
    @Comment("必须存在全局配置项,@ResourceListener才会起作用")
    protected Environment environment;

    @Resource(name = "pay.oppo.conf", required = false) // 支付配置文件路径
    protected String conf = "config.properties";

    @Resource(name = "APP_HOME")
    protected File home;

    @Resource
    protected JsonConvert convert;

    @Override
    public void init(AnyValue conf) {
        super.init(conf);
        if (this.convert == null) {
            this.convert = JsonConvert.root();
        }
        this.reloadConfig(Pays.PAYTYPE_OPPO);
    }

    @Override
    @Comment("判断是否支持指定支付类型")
    public boolean supportPayType(final short payType) {
        return payType == Pays.PAYTYPE_OPPO && !elements.isEmpty();
    }

    @Override
    @Comment("重新加载本地文件配置")
    public void reloadConfig(short payType) {
        Properties properties = new Properties();
        if (this.conf != null && !this.conf.isEmpty()) { // 存在微信支付配置
            try {
                File file = (this.conf.indexOf('/') == 0 || this.conf.indexOf(':') > 0)
                        ? new File(this.conf)
                        : new File(home, "conf/" + this.conf);
                InputStream in = (file.isFile() && file.canRead())
                        ? new FileInputStream(file)
                        : getClass().getResourceAsStream("/META-INF/" + this.conf);
                if (in != null) {
                    properties.load(in);
                    in.close();
                }
            } catch (Exception e) {
                logger.log(Level.SEVERE, "init weixinpay conf error", e);
            }
        }
        this.environment.forEach(k -> k.startsWith("pay.oppo."), (k, v) -> properties.put(k, v));
        this.elements = OppoPayElement.create(logger, properties, home);
        this.elementProps = properties;
    }

    @ResourceChanged //     //
    @Comment("通过配置中心更改配置后的回调")
    void onResourceChanged(ResourceEvent[] events) {
        Properties changeProps = new Properties();
        changeProps.putAll(this.elementProps);
        StringBuilder sb = new StringBuilder();
        for (ResourceEvent event : events) {
            if (event.name().startsWith("pay.oppo.")) {
                changeProps.put(event.name(), event.newValue().toString());
                sb.append("@Resource change '")
                        .append(event.name())
                        .append("' to '")
                        .append(event.coverNewValue())
                        .append("'\r\n");
            }
        }
        if (sb.length() < 1) {
            return; // 无相关配置变化
        }
        logger.log(Level.INFO, sb.toString());
        this.elements = OppoPayElement.create(logger, changeProps, home);
        this.elementProps = changeProps;
    }

    public void setPayElements(Map elements) {
        this.elements = elements;
    }

    public void putPayElements(Map elements) {
        this.elements.putAll(elements);
    }

    @Override
    public OppoPayElement getPayElement(String appid) {
        return this.elements.get(appid);
    }

    public void setPayElement(String appid, OppoPayElement element) {
        this.elements.put(appid, element);
    }

    public boolean existsPayElement(String appid) {
        return this.elements != null && this.elements.containsKey(appid);
    }

    @Override
    public PayPreResponse prepay(PayPreRequest request) {
        return prepayAsync(request).join();
    }

    @Override
    public CompletableFuture prepayAsync(PayPreRequest request) {
        request.checkVaild();
        final PayPreResponse result = new PayPreResponse();
        try {
            final OppoPayElement element = elements.get(request.getAppid());
            if (element == null) {
                return result.retcode(RETPAY_CONF_ERROR).toFuture();
            }
            result.setAppid(element.appid);
            final long timestamp = System.currentTimeMillis();
            final TreeMap map = new TreeMap<>();
            if (request.getAttach() != null) {
                map.putAll(request.getAttach()); // 含openId、appVersion、engineVersion
            }
            map.put("appId", element.appid);
            map.put("timestamp", "" + timestamp);
            map.put("productName", request.getPayTitle());
            map.put("productDesc", request.getPayBody());
            map.put("cpOrderId", request.getPayno());
            map.put("price", "" + request.getPayMoney());
            map.put("count", "1");
            map.put("currency", "CNY");
            map.put("ip", request.getClientAddr());
            map.put(
                    "callBackUrl",
                    ((request.notifyUrl != null && !request.notifyUrl.isEmpty())
                            ? request.notifyUrl
                            : element.notifyurl));
            map.put("sign", createSign(element, map, null));

            return postHttpContentAsync(
                            "    https://jits.open.oppomobile.com/jitsopen/api/pay/v1.0/preOrder", joinMap(map))
                    .thenApply(responseText -> {
                        result.setResponseText(responseText);

                        OppoPrePayResult preresult =
                                JsonConvert.root().convertFrom(OppoPrePayResult.class, responseText);
                        if (!"200".equals(preresult.code)) {
                            return result.retcode(RETPAY_PAY_ERROR);
                        }
                        if (preresult.data == null) {
                            return result.retcode(RETPAY_PAY_ERROR);
                        }
                        if (preresult.data.orderNo == null) {
                            return result.retcode(RETPAY_PAY_ERROR);
                        }
                        result.setThirdPayno(preresult.data.orderNo);
                        final Map retmap = new TreeMap<>();
                        retmap.put("appId", element.appid);
                        retmap.put("token", map.get("token"));
                        retmap.put("timestamp", "" + timestamp);
                        retmap.put("orderNo", preresult.data.orderNo);
                        final TreeMap signmap = new TreeMap<>();
                        signmap.put("appKey ", element.appkey);
                        signmap.put("orderNo ", preresult.data.orderNo);
                        signmap.put("timestamp ", "" + timestamp);
                        retmap.put("paySign", createSign(element, signmap, null));
                        result.setResult(retmap);
                        return result;
                    });
        } catch (Exception e) {
            result.setRetcode(RETPAY_PAY_ERROR);
            logger.log(Level.WARNING, "prepay_pay_error req=" + request + ", resp=" + result.responseText, e);
            return result.toFuture();
        }
    }

    @Override
    public PayNotifyResponse notify(PayNotifyRequest request) {
        return notifyAsync(request).join();
    }

    @Override
    public CompletableFuture notifyAsync(PayNotifyRequest request) {
        request.checkVaild();
        final PayNotifyResponse result = new PayNotifyResponse();
        result.setPayType(request.getPayType());
        final String rstext =
                "";
        Map map = null;
        String appid = request.getAppid();
        if (appid == null || appid.isEmpty()) {
            appid = map.getOrDefault("appid", "");
        }
        final OppoPayElement element = elements.get(appid);
        if (element == null) {
            return result.retcode(RETPAY_CONF_ERROR).toFuture();
        }
        result.setPayno(map.getOrDefault("out_trade_no", ""));
        result.setThirdPayno(map.getOrDefault("transaction_id", ""));
        if ("NOTPAY".equals(map.get("return_code"))) {
            return result.retcode(RETPAY_PAY_WAITING).notifytext(rstext).toFuture();
        }
        if (!"SUCCESS".equals(map.get("return_code"))) {
            return result.retcode(RETPAY_PAY_FAILED).notifytext(rstext).toFuture();
        }
        if (!(map instanceof SortedMap)) {
            map = new TreeMap<>(map);
        }
        if (!checkSign(
                element,
                map,
                null,
                request.getHeaders() == null ? null : request.getHeaders().map())) {
            return result.retcode(RETPAY_FALSIFY_ERROR).notifytext(rstext).toFuture();
        }
        String state = map.get("trade_state");
        if (state == null && "SUCCESS".equals(map.get("result_code")) && Long.parseLong(map.get("total_fee")) > 0) {
            state = "SUCCESS";
            result.setPayedMoney(Long.parseLong(map.get("total_fee")));
        }
        if (!"SUCCESS".equals(state)) {
            return result.retcode(RETPAY_PAY_FAILED).notifytext(rstext).toFuture();
        }
        return result.notifytext(rstext).toFuture();
    }

    @Override
    public PayCreatResponse create(PayCreatRequest request) {
        return createAsync(request).join();
    }

    @Override
    public CompletableFuture createAsync(PayCreatRequest request) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public PayQueryResponse query(PayRequest request) {
        return queryAsync(request).join();
    }

    @Override
    public CompletableFuture queryAsync(PayRequest request) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public PayResponse close(PayCloseRequest request) {
        return closeAsync(request).join();
    }

    @Override
    public CompletableFuture closeAsync(PayCloseRequest request) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public PayRefundResponse refund(PayRefundRequest request) {
        return refundAsync(request).join();
    }

    @Override
    public CompletableFuture refundAsync(PayRefundRequest request) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public PayRefundResponse queryRefund(PayRefundQryReq request) {
        return queryRefundAsync(request).join();
    }

    @Override
    public CompletableFuture queryRefundAsync(PayRefundQryReq request) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    protected String createSign(final PayElement element, Map map, String text) { // 计算签名
        final StringBuilder sb = new StringBuilder();
        map.forEach((x, y) -> {
            if (!((String) y).isEmpty()) {
                sb.append(x).append('=').append(y).append('&');
            }
        });
        try {
            KeyFactory keyf = KeyFactory.getInstance("RSA");
            PKCS8EncodedKeySpec spec = map.containsKey("orderNo")
                    ? ((OppoPayElement) element).appkeyPKCS8
                    : ((OppoPayElement) element).signkeyPKCS8;
            PrivateKey priKey = keyf.generatePrivate(spec);
            java.security.Signature signature = java.security.Signature.getInstance("SHA256WithRSA");

            signature.initSign(priKey);
            signature.update(sb.toString().getBytes(StandardCharsets.UTF_8));

            byte[] signed = signature.sign();
            return Utility.binToHexString(Base64.getEncoder().encode(signed));
        } catch (Exception ex) {
            throw new RedkaleException(ex);
        }
    }

    @Override
    protected boolean checkSign(
            final PayElement element, Map map, String text, Map respHeaders) { // 验证签名
        if (!(map instanceof SortedMap)) {
            map = new TreeMap<>(map);
        }
        String sign = (String) map.remove("sign");
        final StringBuilder sb = new StringBuilder();
        map.forEach((x, y) -> {
            if (!((String) y).isEmpty()) {
                sb.append(x).append('=').append(y).append('&');
            }
        });
        try {
            KeyFactory keyf = KeyFactory.getInstance("RSA");
            PrivateKey priKey = keyf.generatePrivate(((OppoPayElement) element).signkeyPKCS8);
            java.security.Signature signature = java.security.Signature.getInstance("SHA256WithRSA");
            signature.initSign(priKey);
            signature.update(sb.toString().getBytes(StandardCharsets.UTF_8));

            byte[] signed = signature.sign();
            return sign.equals(Utility.binToHexString(Base64.getEncoder().encode(signed)));
        } catch (Exception e) {
            return false;
        }
    }

    public static class OppoPrePayResult {

        public String code;

        public String msg;

        public OppoPrePayResultData data;

        @Override
        public String toString() {
            return JsonConvert.root().convertTo(this);
        }
    }

    public static class OppoPrePayResultData {

        public String appId;

        public String cpOrderId;

        public String orderNo;

        @Override
        public String toString() {
            return JsonConvert.root().convertTo(this);
        }
    }

    public static class OppoPayElement extends PayElement {

        // pay.oppo.[x].appid
        public String appid = ""; // APP应用ID

        // pay.oppo.[x].appkey
        public String appkey = ""; // 支付签名用到的密钥

        // pay.oppo.[x].signkey
        public String signkey = ""; // 签名算法需要用到的密钥

        public PKCS8EncodedKeySpec appkeyPKCS8;

        public PKCS8EncodedKeySpec signkeyPKCS8;

        public static Map create(Logger logger, Properties properties, File home) {
            String def_appid = properties.getProperty("pay.oppo.appid", "").trim();
            String def_appkey = properties.getProperty("pay.oppo.appkey", "").trim();
            String def_signkey = properties.getProperty("pay.oppo.signkey", "").trim();
            String def_notifyurl =
                    properties.getProperty("pay.oppo.notifyurl", "").trim();

            final Map map = new HashMap<>();
            properties.keySet().stream()
                    .filter(x ->
                            x.toString().startsWith("pay.oppo.") && x.toString().endsWith(".appid"))
                    .forEach(appid_key -> {
                        final String prefix = appid_key
                                .toString()
                                .substring(0, appid_key.toString().length() - ".appid".length());

                        String appid = properties
                                .getProperty(prefix + ".appid", def_appid)
                                .trim();
                        String appkey = properties
                                .getProperty(prefix + ".appkey", def_appkey)
                                .trim();
                        String signkey = properties
                                .getProperty(prefix + ".signkey", def_signkey)
                                .trim();
                        String notifyurl = properties
                                .getProperty(prefix + ".notifyurl", def_notifyurl)
                                .trim();

                        if (appid.isEmpty() || notifyurl.isEmpty() || appkey.isEmpty() || signkey.isEmpty()) {
                            logger.log(Level.WARNING, properties + "; has illegal oppopay conf by prefix" + prefix);
                            return;
                        }
                        OppoPayElement element = new OppoPayElement();
                        element.appid = appid;
                        element.appkey = appkey;
                        element.signkey = signkey;
                        element.notifyurl = notifyurl;
                        if (element.initElement(logger, home)) {
                            map.put(appid, element);
                            if (def_appid.equals(appid)) {
                                map.put("", element);
                            }
                        }
                    });
            return map;
        }

        @Override
        public boolean initElement(Logger logger, File home) {
            appkeyPKCS8 = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(appkey));
            signkeyPKCS8 = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(signkey));
            return true;
        }

        @Override
        public String toString() {
            return JsonConvert.root().convertTo(this);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy