diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 5bdc5c2..7efb205 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -67,21 +67,21 @@ spring: enabled: true # redis 配置 redis: - host: 116.204.124.80 - # 端口,默认为6379 - port: 16379 - # 数据库索引 - database: 0 - # 密码 - password: Lwz19520416443@ - # 地址 -# host: 127.0.0.1 +# host: 116.204.124.80 # # 端口,默认为6379 -# port: 6379 +# port: 16379 # # 数据库索引 # database: 0 # # 密码 -# password: +# password: Lwz19520416443@ + # 地址 + host: 127.0.0.1 + # 端口,默认为6379 + port: 6379 + # 数据库索引 + database: 0 + # 密码 + password: # 连接超时时间 timeout: 10s lettuce: diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/vo/OrderInfoVO.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/vo/OrderInfoVO.java index 21b0a79..7960ba3 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/vo/OrderInfoVO.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/vo/OrderInfoVO.java @@ -2,57 +2,61 @@ package com.ruoyi.common.core.domain.vo; import com.fasterxml.jackson.annotation.JsonFormat; import com.ruoyi.common.annotation.Excel; +import lombok.Data; import java.math.BigDecimal; import java.util.Date; /** * 订单信息VO - * + * * @author ruoyi * @date 2025-01-27 */ +@Data public class OrderInfoVO { - + /** 订单号 */ @Excel(name = "订单号") private Long orderId; - + /** 用户昵称 */ @Excel(name = "用户昵称") private String userNickname; - + /** 套餐名称 */ @Excel(name = "套餐名称") private String packageName; - + /** 支付金额(元) */ @Excel(name = "支付金额(元)") private BigDecimal amountYuan; - + /** 支付方式 */ @Excel(name = "支付方式") private String payTypeName; - + /** 开通时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @Excel(name = "开通时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") private Date startTime; - + /** 到期时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @Excel(name = "到期时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") private Date endTime; - + /** 订单状态 */ @Excel(name = "订单状态") private String statusName; - + /** 创建时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") private Date createTime; + private String phone; + public Long getOrderId() { return orderId; } @@ -124,4 +128,4 @@ public class OrderInfoVO { public void setCreateTime(Date createTime) { this.createTime = createTime; } -} \ No newline at end of file +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/util/AppleyPay.java b/ruoyi-system/src/main/java/com/ruoyi/system/util/AppleyPay.java new file mode 100644 index 0000000..e824061 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/util/AppleyPay.java @@ -0,0 +1,170 @@ +package com.ruoyi.system.util; + +import cn.hutool.core.codec.Base64; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.utils.StringUtils; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URL; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.HashMap; + + +/** + * 描述: + * + * @author MXP by 2025/10/28 + */ +@Component +public class AppleyPay { + + private static final Logger log = LoggerFactory.getLogger(AppleyPay.class); + + //购买凭证验证地址 + private static final String certificateUrl = "https://buy.itunes.apple.com/verifyReceipt"; + + //测试的购买凭证验证地址 + private static final String certificateUrlTest = "https://sandbox.itunes.apple.com/verifyReceipt"; + + /** + * 重写X509TrustManager + */ + private static TrustManager myX509TrustManager = new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + }; + + /** + * 接收自己APP客户端发过来的购买凭证 + * + * @param userId 用户ID + * @param receipt 苹果传递前端给的值 + * @param chooseEnv 是否时测试环境 + */ + public String setIapCertificate(String userId, String receipt, boolean chooseEnv) { + log.info("IOS端发送的购买凭证。数据有 userId = {},receipt = {},chooseEnv = {}",userId,receipt,chooseEnv); + if (StringUtils.isEmpty(userId) || StringUtils.isEmpty(receipt)) { + return "用户ID 或者 receipt为空"; + } + String url = null; + url = chooseEnv == true ? certificateUrl : certificateUrlTest; + final String certificateCode = receipt; + if (StringUtils.isNotEmpty(certificateCode)) { + String s = sendHttpsCoon(url, certificateCode, userId); + if ("支付成功".equals(s)) { + return s; + } else { + return s; + } + } else { + return "receipt 为空!"; + } + } + + /** + * 发送请求 向苹果发起验证支付请求是否有效:本方法有认证方法进行调用 + * + * @param url 支付的环境校验 + * @param code 接口传递的 receipt + * @return 结果 + */ + private String sendHttpsCoon(String url, String code, String userId) { + if (url.isEmpty()) { + return null; + } + try { + //设置SSLContext + SSLContext ssl = SSLContext.getInstance("SSL"); + ssl.init(null, new TrustManager[]{myX509TrustManager}, null); + + //打开连接 + HttpsURLConnection conn = (HttpsURLConnection) new URL(url).openConnection(); + //设置套接工厂 + conn.setSSLSocketFactory(ssl.getSocketFactory()); + //加入数据 + conn.setRequestMethod("POST"); + conn.setDoOutput(true); + conn.setRequestProperty("Content-type", "application/json"); + + + JSONObject obj = new JSONObject(); + obj.put("receipt-data", code); + + BufferedOutputStream buffOutStr = new BufferedOutputStream(conn.getOutputStream()); + buffOutStr.write(obj.toString().getBytes()); + buffOutStr.flush(); + buffOutStr.close(); + //获取输入流 + BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); + + String line = null; + StringBuffer sb = new StringBuffer(); + while ((line = reader.readLine()) != null) { + sb.append(line); + } + System.out.println(sb); + // 错误的 sb对象是:{"status":21002},苹果官网写的错误也都是2XXXX 具体含义可查:https://developer.apple.com/documentation/appstorereceipts/status + // 所以 通过长度等于16,我们就可确定苹果支付成功是否有效 + if (sb.length() == 16) { + return "支付失败,苹果说status异常"; + } + // 将一个Json对象转成一个HashMap + JSONObject alljsoncode = JSON.parseObject(sb.toString()); + Object receipt = alljsoncode.get("receipt"); + HashMap hashMap = JSONUtil.toBean(receipt.toString(), HashMap.class); + // 苹果给的订单号 + String original_transaction_id = (String) hashMap.get("original_transaction_id"); + //TODO 存储订单ID,并检查此订单ID是否存在,如果存在就证明已经发货了(避免二次发货) + if ("com.hefeixunliao.zhenliao12yuan".equals(hashMap.get("product_id"))) { + //TODO 执行12元的钻石增加 + log.info("用户ID:{},执行12元钻石追加",userId); + } else if ("com.hefeixunliao.zhenliao30yuan".equals(hashMap.get("product_id"))) { + //TODO 执行30元的钻石增加 + log.info("用户ID:{},执行30元钻石追加",userId); + } else { + log.info("用户ID:{},向苹果发起验证支付请求没有次product_id",userId); + return "支付失败,当前没有次product_id"; + } + return "支付成功"; + } catch (Exception e) { + log.error("向苹果发起验证支付请求是否有效出现异常:{}", e.getMessage()); + return "支付过程中,出现了异常!"; + } + } + + + /** + * 注意:下面代码跟苹果支付业务无关。 + * 这里的code 是前端请求苹果,苹果给前端的一个密钥(如果我们通过base64解密后,可获得signature、purchase-info、environment、pod、signing-status)这个密钥用于告诉Java服务器 想苹果服务器校验订单是否成功的参数 + */ + @Test + public void jiemi() { + String code = "ewoJInNpZ25hdHVyZSIgPSAiQXhmVWRiYUx5T2I5bllOM3hINmQzMnBaOHI2THdmV3ZmZ1NKN1o2QTM4dEY2SjNyUTZoRVZqQ3Rra01wMnhmM1pwWnFQRmw3ZlRIdDVxNVpKZUF6UWh4NWQ1djJrR01uM3NKb3ZBWXNuWENxY3VqclBWU3A5WTFYUTZjeTlvbVNORWNYVWt0L1dkQXhsRmN6WDRZMTJzcktsMDc3WHJIdk5JMDd0VTZXajgzbVdDNE1HZmF0c2E2UEo1RG5sT2lEOG96RlJ6a0NIQ3Y3bncvRm80dnFCaFpZRmlQSDZzeW1uN2lUQlhTcXlTdlJOTGJXLytUWktKZngxR1dRV3BWdmJ5M0RtV3l4OTRaYkxGRllNODE0aTB2a1lnWDdPdVUwQWprTVFKOEJhNnJGc1hER0hYY2FCdnVhZ1NGak9iMWdJclZ2MDdmbTlBV3ltNE5KT0dVSHR0Z0FBQVdBTUlJRmZEQ0NCR1NnQXdJQkFnSUlEdXRYaCtlZUNZMHdEUVlKS29aSWh2Y05BUUVGQlFBd2daWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Td3dLZ1lEVlFRTERDTkJjSEJzWlNCWGIzSnNaSGRwWkdVZ1JHVjJaV3h2Y0dWeUlGSmxiR0YwYVc5dWN6RkVNRUlHQTFVRUF3dzdRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTWdRMlZ5ZEdsbWFXTmhkR2x2YmlCQmRYUm9iM0pwZEhrd0hoY05NVFV4TVRFek1ESXhOVEE1V2hjTk1qTXdNakEzTWpFME9EUTNXakNCaVRFM01EVUdBMVVFQXd3dVRXRmpJRUZ3Y0NCVGRHOXlaU0JoYm1RZ2FWUjFibVZ6SUZOMGIzSmxJRkpsWTJWcGNIUWdVMmxuYm1sdVp6RXNNQ29HQTFVRUN3d2pRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTXhFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4Q3pBSkJnTlZCQVlUQWxWVE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBcGMrQi9TV2lnVnZXaCswajJqTWNqdUlqd0tYRUpzczl4cC9zU2cxVmh2K2tBdGVYeWpsVWJYMS9zbFFZbmNRc1VuR09aSHVDem9tNlNkWUk1YlNJY2M4L1cwWXV4c1FkdUFPcFdLSUVQaUY0MWR1MzBJNFNqWU5NV3lwb041UEM4cjBleE5LaERFcFlVcXNTNCszZEg1Z1ZrRFV0d3N3U3lvMUlnZmRZZUZScjZJd3hOaDlLQmd4SFZQTTNrTGl5a29sOVg2U0ZTdUhBbk9DNnBMdUNsMlAwSzVQQi9UNXZ5c0gxUEttUFVockFKUXAyRHQ3K21mNy93bXYxVzE2c2MxRkpDRmFKekVPUXpJNkJBdENnbDdaY3NhRnBhWWVRRUdnbUpqbTRIUkJ6c0FwZHhYUFEzM1k3MkMzWmlCN2o3QWZQNG83UTAvb21WWUh2NGdOSkl3SURBUUFCbzRJQjF6Q0NBZE13UHdZSUt3WUJCUVVIQVFFRU16QXhNQzhHQ0NzR0FRVUZCekFCaGlOb2RIUndPaTh2YjJOemNDNWhjSEJzWlM1amIyMHZiMk56Y0RBekxYZDNaSEl3TkRBZEJnTlZIUTRFRmdRVWthU2MvTVIydDUrZ2l2Uk45WTgyWGUwckJJVXdEQVlEVlIwVEFRSC9CQUl3QURBZkJnTlZIU01FR0RBV2dCU0lKeGNKcWJZWVlJdnM2N3IyUjFuRlVsU2p0ekNDQVI0R0ExVWRJQVNDQVJVd2dnRVJNSUlCRFFZS0tvWklodmRqWkFVR0FUQ0IvakNCd3dZSUt3WUJCUVVIQWdJd2diWU1nYk5TWld4cFlXNWpaU0J2YmlCMGFHbHpJR05sY25ScFptbGpZWFJsSUdKNUlHRnVlU0J3WVhKMGVTQmhjM04xYldWeklHRmpZMlZ3ZEdGdVkyVWdiMllnZEdobElIUm9aVzRnWVhCd2JHbGpZV0pzWlNCemRHRnVaR0Z5WkNCMFpYSnRjeUJoYm1RZ1kyOXVaR2wwYVc5dWN5QnZaaUIxYzJVc0lHTmxjblJwWm1sallYUmxJSEJ2YkdsamVTQmhibVFnWTJWeWRHbG1hV05oZEdsdmJpQndjbUZqZEdsalpTQnpkR0YwWlcxbGJuUnpMakEyQmdnckJnRUZCUWNDQVJZcWFIUjBjRG92TDNkM2R5NWhjSEJzWlM1amIyMHZZMlZ5ZEdsbWFXTmhkR1ZoZFhSb2IzSnBkSGt2TUE0R0ExVWREd0VCL3dRRUF3SUhnREFRQmdvcWhraUc5Mk5rQmdzQkJBSUZBREFOQmdrcWhraUc5dzBCQVFVRkFBT0NBUUVBRGFZYjB5NDk0MXNyQjI1Q2xtelQ2SXhETUlKZjRGelJqYjY5RDcwYS9DV1MyNHlGdzRCWjMrUGkxeTRGRkt3TjI3YTQvdncxTG56THJSZHJqbjhmNUhlNXNXZVZ0Qk5lcGhtR2R2aGFJSlhuWTR3UGMvem83Y1lmcnBuNFpVaGNvT0FvT3NBUU55MjVvQVE1SDNPNXlBWDk4dDUvR2lvcWJpc0IvS0FnWE5ucmZTZW1NL2oxbU9DK1JOdXhUR2Y4YmdwUHllSUdxTktYODZlT2ExR2lXb1IxWmRFV0JHTGp3Vi8xQ0tuUGFObVNBTW5CakxQNGpRQmt1bGhnd0h5dmozWEthYmxiS3RZZGFHNllRdlZNcHpjWm04dzdISG9aUS9PamJiOUlZQVlNTnBJcjdONFl0UkhhTFNQUWp2eWdhWndYRzU2QWV6bEhSVEJoTDhjVHFBPT0iOwoJInB1cmNoYXNlLWluZm8iID0gImV3b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVdGNITjBJaUE5SUNJeU1ESXhMVEV4TFRJMklESXlPalU1T2pJd0lFRnRaWEpwWTJFdlRHOXpYMEZ1WjJWc1pYTWlPd29KSW5WdWFYRjFaUzFwWkdWdWRHbG1hV1Z5SWlBOUlDSTROelU1WmpNMVlUTTNNMk0wTVRabU5qazRPVFJrWkRRd05XRTFOemhoTURoalpqSTVOMlkwSWpzS0NTSnZjbWxuYVc1aGJDMTBjbUZ1YzJGamRHbHZiaTFwWkNJZ1BTQWlNVEF3TURBd01Ea3hPVE13TWpFeU5DSTdDZ2tpWW5aeWN5SWdQU0FpTVRBd0lqc0tDU0owY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTVRBd01EQXdNRGt4T1RNd01qRXlOQ0k3Q2draWNYVmhiblJwZEhraUlEMGdJakVpT3dvSkltbHVMV0Z3Y0MxdmQyNWxjbk5vYVhBdGRIbHdaU0lnUFNBaVVGVlNRMGhCVTBWRUlqc0tDU0p2Y21sbmFXNWhiQzF3ZFhKamFHRnpaUzFrWVhSbExXMXpJaUE5SUNJeE5qTTNPVGsyTXpZd01qYzNJanNLQ1NKMWJtbHhkV1V0ZG1WdVpHOXlMV2xrWlc1MGFXWnBaWElpSUQwZ0lqaEJSRVJHUlRjMUxURkVRVVl0TkRVek1TMDRNakV3TFVKRE9UZzNSa1l3TlRrNU9DSTdDZ2tpY0hKdlpIVmpkQzFwWkNJZ1BTQWlZMjl0TG1obFptVnBlSFZ1YkdsaGJ5NTZhR1Z1YkdsaGJ6TXdlWFZoYmlJN0Nna2lhWFJsYlMxcFpDSWdQU0FpTVRVNU5qWXpNRGcyT0NJN0Nna2lZbWxrSWlBOUlDSmpiMjB1YUdWbVpXbDRkVzVzYVdGdkxucG9aVzVzYVdGdklqc0tDU0pwY3kxcGJpMXBiblJ5YnkxdlptWmxjaTF3WlhKcGIyUWlJRDBnSW1aaGJITmxJanNLQ1NKd2RYSmphR0Z6WlMxa1lYUmxMVzF6SWlBOUlDSXhOak0zT1RrMk16WXdNamMzSWpzS0NTSndkWEpqYUdGelpTMWtZWFJsSWlBOUlDSXlNREl4TFRFeExUSTNJREEyT2pVNU9qSXdJRVYwWXk5SFRWUWlPd29KSW1sekxYUnlhV0ZzTFhCbGNtbHZaQ0lnUFNBaVptRnNjMlVpT3dvSkluQjFjbU5vWVhObExXUmhkR1V0Y0hOMElpQTlJQ0l5TURJeExURXhMVEkySURJeU9qVTVPakl3SUVGdFpYSnBZMkV2VEc5elgwRnVaMlZzWlhNaU93b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVaUlEMGdJakl3TWpFdE1URXRNamNnTURZNk5UazZNakFnUlhSakwwZE5WQ0k3Q24wPSI7CgkiZW52aXJvbm1lbnQiID0gIlNhbmRib3giOwoJInBvZCIgPSAiMTAwIjsKCSJzaWduaW5nLXN0YXR1cyIgPSAiMCI7Cn0="; + byte[] decode = Base64.decode(code.getBytes()); + System.out.println(new String(decode)); + } + +} diff --git a/ruoyi-system/src/main/resources/mapper/system/OrderInfoMapper.xml b/ruoyi-system/src/main/resources/mapper/system/OrderInfoMapper.xml index f56d0b5..707218f 100644 --- a/ruoyi-system/src/main/resources/mapper/system/OrderInfoMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/OrderInfoMapper.xml @@ -3,7 +3,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - + @@ -35,7 +35,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - + - SELECT + SELECT o.order_id, su.nickname as user_nickname, - CASE + su.phone as phone, + CASE WHEN o.package_type = '1' THEN '包月' - WHEN o.package_type = '3' THEN '包季度' - WHEN o.package_type = '6' THEN '半年' - WHEN o.package_type = '12' THEN '一年' + WHEN o.package_type = '2' THEN '包季度' + WHEN o.package_type = '3' THEN '半年' + WHEN o.package_type = '4' THEN '一年' ELSE '未知套餐' END as package_name, ROUND(o.amount / 100.0, 2) as amount_yuan, - CASE + CASE WHEN o.pay_type = 'aliPay' THEN '支付宝' WHEN o.pay_type = 'wechatPay' THEN '微信支付' WHEN o.pay_type = 'applePay' THEN 'Apple Pay' @@ -189,7 +191,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" END as pay_type_name, o.start_time, o.end_time, - CASE + CASE WHEN o.pay_status = 1 THEN '待支付' WHEN o.pay_status = 2 THEN '待出货' WHEN o.pay_status = 3 THEN '待退款' @@ -217,4 +219,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ORDER BY o.create_time DESC - \ No newline at end of file +