springboot整合pi支付开发

news2024/11/24 3:53:00

pi支付流程图:

  1. 使用Pi SDK功能发起支付
  2. 由 Pi SDK 自动调用的回调函数(让您的应用服务器知道它需要发出批准 API 请求)
  3. 从您的应用程序服务器到 Pi 服务器的 API 请求以批准付款(让 Pi 服务器知道您知道此付款)
  4. Pi浏览器向用户显示付款详细信息页面,我们正在等待用户签署交易
  5. 由 Pi SDK 自动调用的回调函数(让您的应用服务器知道它需要发出完整的 API 请求)
  6. 从您的应用服务器到 Pi 服务器的 API 请求以完成付款(让 Pi 服务器知道您已完成此付款)

支付流程

 引入依赖

  <dependency>
      <groupId>com.squareup.okhttp3</groupId>
      <artifactId>okhttp</artifactId>
      <version>4.10.0-RC1</version>
   </dependency>
   <dependency>
      <groupId>cn.hutool</groupId>
      <artifactId>hutool-all</artifactId>
      <version>5.8.0.M4</version>
   </dependency>
    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
      <version>4.5.13</version>
   </dependency>
        <dependency>
        <groupId>commons-lang</groupId>
        <artifactId>commons-lang</artifactId>
        <version>2.6</version>
   </dependency>

配置api密钥

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

/**
 * 服务器端key
 * @author ThinkPad
 */
@Configuration
@Data
public class CommonConfig {

    @Value("${sdk.serverAccessKey}")
    private String serverAccessKey;
}

 接收和返回数据对象

loginVO接收pi中心来的用户信息

import lombok.Data;

/**
 * @author wzx
 * 登录数据封装类
 */
@Data
public class LoginVO {

    private String userId;
    private String userName;
    private String accessToken;
}

paymentVO接收支付授权的信息

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

/**
 * @author ThinkPad
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PaymentVO {




    private String paymentId;
    // 交易金额
    private BigDecimal amount;
    // 名片对应的用户数据
    private String shopUserId;
    // 商品id
    private String shopId;
    // 当前账号用户的id
    private String userId;

}

completeVO接收支付完成的信息

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author ThinkPad
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CompleteVO {
    // PI支付ID

    private String paymentId;
    // txId

    private String txId;
    // 订单ID【余额支付参数】

    private String orderId;
    // 支付方式:0:PI钱包 1:余额支付

    private String payType;
}

incompleteVO接收未完成订单的信息

/**
 * @author ThinkPad
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class IncompleteVO {

    private String identifier;


    private TransactionVO transaction;
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author ThinkPad
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TransactionVO {


    private String txid;


    private String _link;
}

工具类

发起http请求工具类

import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.ParseException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;


/**
 * @author Ashy.Cheung
 * @http 请求工具类
 * @date 2017.11.10
 */
public class HttpClientUtil {

    public static String sendGet(String url) {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpGet httpget = new HttpGet(url);
        CloseableHttpResponse response = null;
        try {
            response = httpclient.execute(httpget);
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        String result = null;
        try {
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                result = EntityUtils.toString(entity);
            }
        } catch (ParseException | IOException e) {
            e.printStackTrace();
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    /**
     *
     * @param url
     * @param charsetName 返回字符集
     * @return
     */
    public static String sendGet(String url, String charsetName) {
        InputStream inputStream = null;
        HttpURLConnection urlConnection = null;

        try {
            URL url1 = new URL(url);
            urlConnection = (HttpURLConnection) url1.openConnection();
            // 将返回的输入流转换成字符串
            inputStream = urlConnection.getInputStream();
            // 指定编码格式
            if (StringUtils.isBlank(charsetName)) {
                charsetName = "UTF-8";
            }
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, charsetName);
            BufferedReader in = new BufferedReader(inputStreamReader);
            String jsonUserStr = in.readLine();
            return jsonUserStr;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if (null != inputStream) {
                    inputStream.close();
                }
                urlConnection.disconnect();
            } catch (Exception e) {

            }
            try {
                if (null != urlConnection) {
                    urlConnection.disconnect();
                }

            } catch (Exception e) {

            }
        }

    }
    /**
     * 发送HttpPost请求,参数为String
     * 接收端以流形式接收
     */
    public static String sendPost(String url, String param) {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        StringEntity strEntity = null;
        try {
            strEntity = new StringEntity(param, "UTF-8");
            strEntity.setContentType("application/json");
        } catch (Exception e1) {

            e1.printStackTrace();
        }
        HttpPost httppost = new HttpPost(url);
        httppost.setEntity(strEntity);
        CloseableHttpResponse response = null;
        String result = null;
        try {
            response = httpclient.execute(httppost);
            HttpEntity entity1 = response.getEntity();
            result = EntityUtils.toString(entity1);

        } catch (IOException e) {
            //  e.printStackTrace();
        } finally {
            try {
                response.close();
            } catch (Exception e) {

            }
        }

        return result;
    }

    /**
     * 发送不带参数的HttpPost请求
     */
    public static String sendPost(String url) {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpPost httppost = new HttpPost(url);
        CloseableHttpResponse response = null;
        try {
            response = httpclient.execute(httppost);
        } catch (IOException e) {
            e.printStackTrace();
        }
        HttpEntity entity = response.getEntity();
        String result = null;
        try {
            result = EntityUtils.toString(entity);
        } catch (ParseException | IOException e) {
            e.printStackTrace();
        } finally {
            try {
                response.close();
            } catch (Exception e) {

            }
        }
        return result;
    }

}

 分布式锁工具类

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
@Component
public class RedisLockUtil {

    private static final Logger log = LoggerFactory.getLogger(RedisLockUtil.class);

    @Resource
     RedisTemplate<String, Object> redisTemplate;



    /**
     * 释放锁脚本,原子操作,lua脚本
     */
    private static final String UNLOCK_LUA;

    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("    return redis.call(\"del\",KEYS[1]) ");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();

    }

    /**
     * 获取分布式锁,原子操作
     *
     * @param lockKey   锁
     * @param lockValue 唯一ID
     * @param leaseTime 过期时间 秒
     * @return 是否枷锁成功
     */
    public  boolean tryLock(String lockKey, String lockValue, long leaseTime) {
        try {
            RedisCallback<Boolean> callback = (connection) -> connection.set(lockKey.getBytes(StandardCharsets.UTF_8),
                    lockValue.getBytes(StandardCharsets.UTF_8), Expiration.seconds(leaseTime),
                    RedisStringCommands.SetOption.SET_IF_ABSENT);
            return redisTemplate.execute(callback);
        } catch (Exception e) {
            log.error("redis lock error ,lock key: {}, value : {}, error info : {}", lockKey, lockValue, e);
        }
        return false;
    }

    /**
     * 释放锁
     *
     * @param lockKey   锁
     * @param lockValue 唯一ID
     * @return 执行结果
     */
    public  boolean unlock(String lockKey, String lockValue) {
        RedisCallback<Boolean> callback = (connection) -> connection.eval(UNLOCK_LUA.getBytes(), ReturnType.BOOLEAN, 1, lockKey.getBytes(StandardCharsets.UTF_8), lockValue.getBytes(StandardCharsets.UTF_8));
        return redisTemplate.execute(callback);
    }

    /**
     * 获取分布式锁,该方法不再使用
     *
     * @param lockKey   锁
     * @param lockValue 唯一ID
     * @param waitTime  等待时间 秒
     * @param leaseTime 过期时间 秒
     * @return 是否枷锁成功
     */
    @Deprecated
    public   boolean tryLock(String lockKey, String lockValue, long waitTime, long leaseTime) {
        try {
            RedisCallback<Boolean> callback = (connection) -> connection.set(lockKey.getBytes(StandardCharsets.UTF_8),
                    lockValue.getBytes(StandardCharsets.UTF_8), Expiration.seconds(leaseTime),
                    RedisStringCommands.SetOption.SET_IF_ABSENT);
            return redisTemplate.execute(callback);
        } catch (Exception e) {
            log.error("redis lock error ,lock key: {}, value : {}, error info : {}", lockKey, lockValue, e);
        }
        return false;
    }
}

生成uuid工具类

import java.text.SimpleDateFormat;
import java.util.Date;

public class UUID {

    public static String randomUUID() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HH'H'mm'M'ss'S'SSS");
        String id = sdf.format(new Date()) + (int) ((Math.random() * 9 + 1) * 100000000) + (int) ((Math.random() * 9 + 1) * 10);
        return id;
    }

    public static String randomQr() {
        String id = (int) ((Math.random() * 9 + 1) * 1000) + "-" + (int) ((Math.random() * 9 + 1) * 1000) + "-" + (int) ((Math.random() * 9 + 1) * 1000);
        return id;
    }

}

信息返回的枚举

import lombok.AllArgsConstructor;

import lombok.Getter;

/**
 * @author ThinkPad
 */

@Getter
@AllArgsConstructor
public enum PaymentEnum {
    PAYMENT_ENUM_1(1, "订单不存在","失败"),
    PAYMENT_ENUM_2(2,"订单不是待支付状态","失败"),
    PAYMENT_ENUM_3(3,"支付金额少于订单金额","失败"),
    PAYMENT_ENUM_4(4,"调用太快","失败"),
    PAYMENT_ENUM_5(5,"余额不足,请前往充值","失败"),
    PAYMENT_ENUM_6(6,"支付成功","成功"),
    PAYMENT_ENUM_7(7,"处理成功","失败"),
    PAYMENT_ENUM_8(8,"处理失败","失败");
    private final Integer code;
    private final String msg;
    private final String status;

    public static String getMsgByCode(Integer code) {
        for (PaymentEnum value : PaymentEnum.values()) {
            if (value.getCode().equals(code)) {
                return value.getMsg();
            }
        }
        return null;
    }

    public static String getStatusByCode(Integer code) {
        for (PaymentEnum value : PaymentEnum.values()) {
            if (value.getCode().equals(code)) {
                return value.getStatus() ;
            }
        }
        return null;
    }


}

支付的controller层

 /**
     * 处理未完成的订单 (这部十分重要,会影响到后面的操作)
     */
    @PostMapping("payOrder/incomplete")
    @ApiOperation("处理未完成的订单")
    @ApiImplicitParam(name = "Authorization", value = "传入你的令牌",required = true, dataType = "String",paramType="header")
    public ResponseVO incomplete(@RequestBody IncompleteVO incompleteVO) {

        try {

            return orderInfoService.incomplete(incompleteVO);
        } catch (Exception e) {
            log.error("报错如下:{}", e.getMessage());
            throw new BusinessException("支付失败,请联系后台人员"+e.getLocalizedMessage()+e.toString()+e.getCause().toString());
        }

    }

    /**
     * 前端请求支付授权,在本地订单创建后调
     */
    @PostMapping("payOrder/approve")
    @ApiOperation("前端请求支付授权,在本地订单创建后调")
    @ApiImplicitParam(name = "Authorization", value = "传入你的令牌",required = true, dataType = "String",paramType="header")
    public ResponseVO<String> approve(@RequestBody PaymentVO paymentVO) {

        try {

            String orderId = orderInfoService.approve(paymentVO);

            return ResponseVO.getSuccessResponseVo(orderId);
        } catch (Exception e) {
            log.error("报错如下:{}", e.getMessage());
            throw new BusinessException("支付失败,请联系后台人员"+e.getLocalizedMessage()+e.toString()+e.getCause().toString());
        }

    }


    /**
     * 前端支付完成,余额支付直接调用此方法
     */
    @PostMapping("payOrder/complete")
    @ApiOperation("前端支付完成,余额支付直接调用此方法")
    @ApiImplicitParam(name = "Authorization", value = "传入你的令牌",required = true, dataType = "String",paramType="header")
    public ResponseVO complete(@RequestBody CompleteVO completeVO) {

        try {


            return orderInfoService.complete(completeVO);
        } catch (Exception e) {
            log.error("报错如下:{}", e.getMessage());
            throw new BusinessException("支付失败,请联系后台人员"+e.getLocalizedMessage()+e.toString()+e.getCause().toString());
        }

    }

    /**
     * 取消支付,订单关闭
     */
    @PostMapping("payOrder/cancelled")
    @ApiOperation("取消支付,订单关闭")
    @ApiImplicitParam(name = "Authorization", value = "传入你的令牌",required = true, dataType = "String",paramType="header")
    public ResponseVO<String> cancelled(@RequestBody String orderId) {

        try {

            Boolean order = orderInfoService.cancelled(orderId);
//            if (!order){throw  new BusinessException("取消订单失败");}
            return ResponseVO.getSuccessResponseVo("取消订单成功");
        } catch (Exception e) {
            log.error("报错如下:{}", e.getMessage());
            throw new BusinessException("取消失败,请联系后台人员"+e.getLocalizedMessage()+e.toString()+e.getCause().toString());
        }

    }

支付的service层

    /**
     * 请求支付授权,创建order。返回orderId
     * @param paymentVO
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String approve(PaymentVO paymentVO) {
        log.error("approve-------------------------------------------------------------");
        OrderInfo orderInfo;
        log.error("paymentVO----------------------------------"+paymentVO);
        //获取付款信息
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url("https://api.minepi.com/v2/payments/" + paymentVO.getPaymentId())
                .addHeader("Authorization", "Key " + commonConfig.getServerAccessKey())
                .build();
        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                String string = response.body().string();
                JSONObject jsonObject1 = JSON.parseObject(string);
                log.error("!response-------------------------------------------------------------"+commonConfig.getServerAccessKey());
                throw new RuntimeException("payments error " + jsonObject1.getString("error_message"));
            }
            String string = response.body().string();
            log.error("response-------------------------------------------------------------"+string);

            JSONObject jsonObject1 = JSON.parseObject(string);
            //校验实际支付金额
            BigDecimal userFinalPrice = paymentVO.getAmount();


            if (userFinalPrice.compareTo(jsonObject1.getBigDecimal("amount")) < 0) {
                log.error(userFinalPrice+"response-------------------------------------------------------------"+jsonObject1.getBigDecimal("amount"));
                throw new RuntimeException("支付金额少于订单金额");
            }


        } catch (Exception e) {
            throw  new BusinessException("支付失败,请联系后台人员"+e.getLocalizedMessage()+e.toString()+e.getCause().toString());
        }


        OkHttpClient client1 = new OkHttpClient();
        //信息真实,通知PI我准备好了,可以付款了
        Request request1 = new Request.Builder()
                .url("https://api.minepi.com/v2/payments/" + paymentVO.getPaymentId() + "/approve")
                .addHeader("Content-Type", "application/json")
                .addHeader("Access-Control-Allow-Origin", "*")
                .addHeader("Authorization", "Key " + commonConfig.getServerAccessKey())
                .post(RequestBody.create("", MediaType.parse("application/json")))
                .build();
        try (Response response1 = client1.newCall(request1).execute()) {
            if (!response1.isSuccessful()) {
                throw new RuntimeException("approve error: ");
            }
            log.error("response1-------------------------------------------------------------");

            //更新支付报文
//                    tMerStoreGoodsOrderEntity.setPayOrderId(paymentDto.getPaymentId());
//                    tMerStoreGoodsOrderEntity.setPayStatusType("10007002");//支付中
//                    itMerStoreGoodsOrderService.updateById(tMerStoreGoodsOrderEntity);
            log.error("return-------------------------------------------------------------");
        } catch (RuntimeException | IOException e) {
            log.error("error-------------------------------------------------------------");
            e.printStackTrace();
        }
        // 生成订单
        orderInfo = new OrderInfo();
        orderInfo.setOrderId(StringUtil.generateShortId());
        orderInfo.setShopId(paymentVO.getShopId());
        orderInfo.setUserId(paymentVO.getUserId());
        orderInfo.setShopUserId(paymentVO.getShopUserId());
        orderInfo.setAmount(paymentVO.getAmount());
        orderInfoMapper.insert(orderInfo);
        log.error("生成订单-------------------------------------------------------------");
        return orderInfo.getOrderId();
    }
    /**
     * 前端支付完成,余额支付直接调用此方法
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseVO complete(CompleteVO completeVO) {
        String payType = completeVO.getPayType();
        log.error("complete------------------------------------------------------------"+completeVO);
        if ("1".equals(payType)) {
            //余额支付
            String orderId = completeVO.getOrderId();
            String lockName = "access:lock:complete:" + orderId;
            String lockId = UUID.randomUUID();
            if (!redisLockUtil.tryLock(lockName, lockId, 20L)) {
                // 调用太快
                return new ResponseVO(PaymentEnum.getStatusByCode(4),4,PaymentEnum.getMsgByCode(4));
            }
            // 获取订单信息
            OrderInfo orderInfo = orderInfoMapper.selectOne(new QueryWrapper<OrderInfo>().eq("order_id", orderId));

            if ((orderInfo.getOrderStatus() != 0)) {
                // 订单不是待支付状态
                return new ResponseVO(PaymentEnum.getStatusByCode(2),2,PaymentEnum.getMsgByCode(2));
            }
            String userId = orderInfo.getUserId();
            AccountInfo accountInfo = accountInfoMapper.selectOne(new QueryWrapper<AccountInfo>()
                                                                        .eq("user_id", userId));

            BigDecimal balance = accountInfo.getPiBalance();
            if (balance.compareTo(orderInfo.getAmount()) < 0) {
                // 余额不足,请前往充值
                return new ResponseVO(PaymentEnum.getStatusByCode(5),5,PaymentEnum.getMsgByCode(5));
            }
            int update = orderInfoMapper.update(null,new UpdateWrapper<OrderInfo>()
                                                                .eq("order_id",orderId)
                                                                .set("order_status",1));
            balance=balance.subtract(orderInfo.getAmount());
            int update1 = accountInfoMapper.update(null, new UpdateWrapper<AccountInfo>()
                                                                    .eq("user_id", userId)
                                                                    .set("pi_balance", balance));
            // 支付成功
            return new ResponseVO(PaymentEnum.getStatusByCode(6),6,PaymentEnum.getMsgByCode(6));
        }
        //PI钱包支付
        String paymentId = completeVO.getPaymentId();//PI订单号
        String lockName = "access:lock:complete:" + paymentId;
        String lockId = UUID.randomUUID();
        log.error(paymentId+"-----------------"+lockName+"---------------------"+lockId);
        if (!redisLockUtil.tryLock(lockName, lockId, 20L)) {
            // 调用太快
            log.error("!RedisLockUtil---------------------------------------------------------调用太快");
            return new ResponseVO(PaymentEnum.getStatusByCode(4),4,PaymentEnum.getMsgByCode(4));
        }
        OrderInfo orderInfo = orderInfoMapper.selectOne(new QueryWrapper<OrderInfo>()
                                                            .eq("order_id", completeVO.getOrderId()));
        log.error("orderId--------------------------------------------------------------"+orderInfo);
        if (null == orderInfo) {
            // 订单不存在
            log.error("!orderinfo--------------------------------------------------------不存在");
            return new ResponseVO(PaymentEnum.getStatusByCode(1),1,PaymentEnum.getMsgByCode(1));
        }
        log.error("orderinfo------------------------------------------------------------------"+orderInfo);
        if (orderInfo.getOrderStatus() != 0) {
            // 订单不是待支付状态
            log.error("!order---------------------------------------------------------pay");
            return  new ResponseVO(PaymentEnum.getStatusByCode(2),2,PaymentEnum.getMsgByCode(2));
        }


        //通知PI完成交易
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("txid", completeVO.getTxId());

        Map<String, String> heads = new HashMap<>();
        heads.put("Content-Type", "application/json;charset=UTF-8");
        heads.put("Authorization", "Key " + commonConfig.getServerAccessKey());
        log.error("pi-----------------------------------------"+jsonObject);
        try {
            HttpResponse response = HttpRequest.post("https://api.minepi.com/v2/payments/" + paymentId + "/complete")
                    .headerMap(heads, false)
                    .body(String.valueOf(jsonObject))
                    .timeout(5 * 60 * 1000)
                    .execute();
            String body = response.body();
            JSONObject jsonObject1 = JSON.parseObject(body);
            String error = jsonObject1.getString("error");
            if (!StringUtils.isEmpty(error)) {
                log.error("!strinutils-----------------------------"+body);
                throw new RuntimeException("订单完成异常!");
            }
            orderInfo.setOrderStatus(1);
            // 更新订单
            orderInfoMapper.updateById(orderInfo);
            log.error("支付成功------------------------------------------------------");
            // 支付成功
            return  new ResponseVO(PaymentEnum.getStatusByCode(6),6,PaymentEnum.getMsgByCode(6));
        } catch (Exception e) {
            throw e;
        }

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean cancelled(String orderId) {
        int update = orderInfoMapper.update(null, new UpdateWrapper<OrderInfo>()
                                                        .eq("order_id", orderId)
                                                        .set("order_status", 3));
        return update > 0;
    }

    @Override
    public ResponseVO incomplete(IncompleteVO incompleteVO) {
        log.error("incomplete--------------------------------");
        try {
            //先处理未完成的订单
            String oldpaymentId = incompleteVO.getIdentifier();
            TransactionVO transaction = incompleteVO.getTransaction();
            log.error("?transation--------------------"+transaction);
            log.error("?oldpaymentId------------------"+oldpaymentId);
            if (null != transaction) {
                log.error("transation--------------------"+transaction);
                log.error("oldpaymentId------------------"+oldpaymentId);
                String txid = transaction.getTxid();
                String txURL = transaction.get_link();

//                OrderInfo orderInfo = orderInfoMapper.selectOne(new QueryWrapper<OrderInfo>().eq("order_id", oldpaymentId));
//                if (null == orderInfo) {
//                    log.error("order-----------------null");
//                    throw new RuntimeException("旧订单不存在");
//                }
//
//                if (orderInfo.getOrderStatus()==1) {
//                    log.error("orderStatus---------------------"+orderInfo.getOrderStatus());
//                    throw new RuntimeException("订单是已支付状态");
//                }

                String get = HttpClientUtil.sendGet(txURL);
                JSONObject jsonObject1 = JSON.parseObject(get);
                String piOrderId = jsonObject1.getString("memo");//我方订单ID

                log.error("memo---------------------"+piOrderId);

                JSONObject jsonObject = new JSONObject();
                jsonObject.put("txid", txid);

                Map<String, String> heads = new HashMap<>();
                heads.put("Content-Type", "application/json;charset=UTF-8");
                heads.put("Authorization", "Key " + commonConfig.getServerAccessKey());

                try {
                    HttpResponse response = HttpRequest.post("https://api.minepi.com/v2/payments/" + piOrderId + "/complete")
                            .headerMap(heads, false)
                            .body(String.valueOf(jsonObject))
                            .timeout(5 * 60 * 1000)
                            .execute();
                    String body = response.body();
                    JSONObject jsonObject2 = JSON.parseObject(body);
                    String error = jsonObject2.getString("error");
                    if (!StringUtils.isEmpty(error)) {
                        log.error("!response------------------"+error);
                        throw new RuntimeException("订单完成异常!");
                    }

                    return new ResponseVO(PaymentEnum.getStatusByCode(7),7,PaymentEnum.getMsgByCode(7));
                } catch (Exception e) {
                    throw e;
                }
            }
        } catch (Exception e) {
            return new ResponseVO(PaymentEnum.getStatusByCode(8),8,PaymentEnum.getMsgByCode(8));
        }
        return new ResponseVO(PaymentEnum.getStatusByCode(8),8,PaymentEnum.getMsgByCode(8));
    }

 前端代码

前端路由 

API
// 授权
import request from "@/api/http";
import axios from "axios";

// 支付授权
export function payAuth(data){
  return request({
    url: "/api/order/payOrder/approve",
    method: "post",
    data,
  });
}

//未完成订单
export function payIncomplete(data){
  return request({
    url: "/api/order/payOrder/incomplete",
    method: "post",
    data,
  });
}

//支付成功
export function payDone(data){
  return request({
    url: "/api/order/payOrder/complete",
    method: "post",
    data,
  });
}
//支付取消
export function payCancel(data){
  return request({
    url: "/api/order/payOrder/cancelled",
    method: "post",
    data,
  })
}

支付前端代码

 test(){
      const pay_message = this.pay_message
      let orderid = ''
      console.log({
        amount: pay_message.amount,
        shopUserId: pay_message.shopUserId,
        shopId: pay_message.shopId,
        userId: pay_message.userId
      })
      Pi.createPayment({
        // Amount of π to be paid:
        amount: 3.14,
        // An explanation of the payment - will be shown to the user:
        memo: "购买特殊数据", // e.g: "Digital kitten #1234",
        // An arbitrary developer-provided metadata object - for your own usage:
        metadata: { productID : 'apple_pie_1'  }, // e.g: { kittenId: 1234 }
      }, {
        // Callbacks you need to implement - read more about those in the detailed docs linked below:

        // 授权
        async onReadyForServerApproval(paymentId) {
          console.log('paymentId',paymentId)
          payAuth({
            paymentId: paymentId,
            amount: pay_message.amount,
            shopUserId: pay_message.shopUserId,
            shopId: pay_message.shopId,
            userId: pay_message.userId
          }).then((data) => {
            orderid = data.data
            console.log('orderId',orderid)
          }).catch(error => {
            console.log(error)
          })
        },
        //支付成功
        onReadyForServerCompletion: function(paymentId, txid) {
          alert(1111)
          console.log(paymentId, 'paymentId', 'txid', txid,'orderid',orderid )
          payDone({paymentId: paymentId, txId: txid, orderId: orderid,payType:'0'}).then(res => {
            console.log(res)
            // if (res && res.code === 0) {
            //   this.payDoneJump();
            // }
          })
        },
        //支付取消
        onCancel: function(orderid) {
          console.log('onCancel' + orderid)
            payCancel(orderid).then((data) => {
              console.log(data)
            })
        },
        //支付失败
        onError: function(error, payment) {console.log('error:',error);console.log('payment:',payment)}
      });
    },

登录自动调用未支付订单,这个十分重要因为会影响支付授权。 

const loginFun = () => {
  Pi.init({ version: "2.0", sandbox: true });

  const scopes = ["payments", "username", "wallet_address"];

  function onIncompletePaymentFound(payment) {
    alert(1111111)
    console.log("payment", payment);
    return payIncomplete({
      identifier:payment.identifier,
      transaction:{
        _link:payment.transaction._link,
        txid:res.transaction.txid
      }
    })
  }
  Pi.authenticate(scopes, onIncompletePaymentFound).then(function (auth) {
      console.log("auth", auth);
      let userInfo = {
        accessToken: auth.accessToken,
        userId: auth.user.uid,
        userName: auth.user.username,
      };

      // userGetPush().then((data) => {
      //   console.log(data);
      //   userStore().userGetPush = data.data;
      // });

      Login(userInfo).then((data) => {
        console.log(data);

        if (data.status == "success") {
          // 将用户信息存入pinia
          userStore().userInfoChange(data.data);

          // 发布消息到socket Login() 存入userId
          // this.$socket.emit("login", data.data.userInfo.userId);

          router.push("/home");
        }
      });
    })
    .catch(function (error) {
      console.error(error);
    });
};

详细流程

使用Pi-SDK功能发起支付

 由Pi SDK自动调用的回调函数,发出支付批准请求

 

 路由到后端的支付授权接口

 后端服务器向Pi服务器发起支付授权

 @Override
    @Transactional(rollbackFor = Exception.class)
    public String approve(PaymentVO paymentVO) {
        log.error("approve-------------------------------------------------------------");
        OrderInfo orderInfo;
        log.error("paymentVO----------------------------------"+paymentVO);
        //获取付款信息
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url("https://api.minepi.com/v2/payments/" + paymentVO.getPaymentId())
                .addHeader("Authorization", "Key " + commonConfig.getServerAccessKey())
                .build();
        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                String string = response.body().string();
                JSONObject jsonObject1 = JSON.parseObject(string);
                log.error("!response-------------------------------------------------------------"+commonConfig.getServerAccessKey());
                throw new RuntimeException("payments error " + jsonObject1.getString("error_message"));
            }
            String string = response.body().string();
            log.error("response-------------------------------------------------------------"+string);

            JSONObject jsonObject1 = JSON.parseObject(string);
            //校验实际支付金额
            BigDecimal userFinalPrice = paymentVO.getAmount();


            if (userFinalPrice.compareTo(jsonObject1.getBigDecimal("amount")) < 0) {
                log.error(userFinalPrice+"response-------------------------------------------------------------"+jsonObject1.getBigDecimal("amount"));
                throw new RuntimeException("支付金额少于订单金额");
            }


        } catch (Exception e) {
            throw  new BusinessException("支付失败,请联系后台人员"+e.getLocalizedMessage()+e.toString()+e.getCause().toString());
        }


        OkHttpClient client1 = new OkHttpClient();
        //信息真实,通知PI我准备好了,可以付款了
        Request request1 = new Request.Builder()
                .url("https://api.minepi.com/v2/payments/" + paymentVO.getPaymentId() + "/approve")
                .addHeader("Content-Type", "application/json")
                .addHeader("Access-Control-Allow-Origin", "*")
                .addHeader("Authorization", "Key " + commonConfig.getServerAccessKey())
                .post(RequestBody.create("", MediaType.parse("application/json")))
                .build();
        try (Response response1 = client1.newCall(request1).execute()) {
            if (!response1.isSuccessful()) {
                throw new RuntimeException("approve error: ");
            }
            log.error("response1-------------------------------------------------------------");

            //更新支付报文
//                    tMerStoreGoodsOrderEntity.setPayOrderId(paymentDto.getPaymentId());
//                    tMerStoreGoodsOrderEntity.setPayStatusType("10007002");//支付中
//                    itMerStoreGoodsOrderService.updateById(tMerStoreGoodsOrderEntity);
            log.error("return-------------------------------------------------------------");
        } catch (RuntimeException | IOException e) {
            log.error("error-------------------------------------------------------------");
            e.printStackTrace();
        }
        // 生成订单
        orderInfo = new OrderInfo();
        orderInfo.setOrderId(StringUtil.generateShortId());
        orderInfo.setShopId(paymentVO.getShopId());
        orderInfo.setUserId(paymentVO.getUserId());
        orderInfo.setShopUserId(paymentVO.getShopUserId());
        orderInfo.setAmount(paymentVO.getAmount());
        orderInfoMapper.insert(orderInfo);
        log.error("生成订单-------------------------------------------------------------");
        return orderInfo.getOrderId();
    }

PI游览器向用户显示付款详细信息页面,我们等待用户签署交易

 由Pi SDK自动调用完成的回调函数

 从你的后端服务器到Pi服务器的API请求以完成付款(让pi服务器知道你完成此付款)

 /**
     * 前端支付完成,余额支付直接调用此方法
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseVO complete(CompleteVO completeVO) {
        String payType = completeVO.getPayType();
        log.error("complete------------------------------------------------------------"+completeVO);
        if ("1".equals(payType)) {
            //余额支付
            String orderId = completeVO.getOrderId();
            String lockName = "access:lock:complete:" + orderId;
            String lockId = UUID.randomUUID();
            if (!redisLockUtil.tryLock(lockName, lockId, 20L)) {
                // 调用太快
                return new ResponseVO(PaymentEnum.getStatusByCode(4),4,PaymentEnum.getMsgByCode(4));
            }
            // 获取订单信息
            OrderInfo orderInfo = orderInfoMapper.selectOne(new QueryWrapper<OrderInfo>().eq("order_id", orderId));

            if ((orderInfo.getOrderStatus() != 0)) {
                // 订单不是待支付状态
                return new ResponseVO(PaymentEnum.getStatusByCode(2),2,PaymentEnum.getMsgByCode(2));
            }
            String userId = orderInfo.getUserId();
            AccountInfo accountInfo = accountInfoMapper.selectOne(new QueryWrapper<AccountInfo>()
                                                                        .eq("user_id", userId));

            BigDecimal balance = accountInfo.getPiBalance();
            if (balance.compareTo(orderInfo.getAmount()) < 0) {
                // 余额不足,请前往充值
                return new ResponseVO(PaymentEnum.getStatusByCode(5),5,PaymentEnum.getMsgByCode(5));
            }
            int update = orderInfoMapper.update(null,new UpdateWrapper<OrderInfo>()
                                                                .eq("order_id",orderId)
                                                                .set("order_status",1));
            balance=balance.subtract(orderInfo.getAmount());
            int update1 = accountInfoMapper.update(null, new UpdateWrapper<AccountInfo>()
                                                                    .eq("user_id", userId)
                                                                    .set("pi_balance", balance));
            // 支付成功
            return new ResponseVO(PaymentEnum.getStatusByCode(6),6,PaymentEnum.getMsgByCode(6));
        }
        //PI钱包支付
        String paymentId = completeVO.getPaymentId();//PI订单号
        String lockName = "access:lock:complete:" + paymentId;
        String lockId = UUID.randomUUID();
        log.error(paymentId+"-----------------"+lockName+"---------------------"+lockId);
        if (!redisLockUtil.tryLock(lockName, lockId, 20L)) {
            // 调用太快
            log.error("!RedisLockUtil---------------------------------------------------------调用太快");
            return new ResponseVO(PaymentEnum.getStatusByCode(4),4,PaymentEnum.getMsgByCode(4));
        }
        OrderInfo orderInfo = orderInfoMapper.selectOne(new QueryWrapper<OrderInfo>()
                                                            .eq("order_id", completeVO.getOrderId()));
        log.error("orderId--------------------------------------------------------------"+orderInfo);
        if (null == orderInfo) {
            // 订单不存在
            log.error("!orderinfo--------------------------------------------------------不存在");
            return new ResponseVO(PaymentEnum.getStatusByCode(1),1,PaymentEnum.getMsgByCode(1));
        }
        log.error("orderinfo------------------------------------------------------------------"+orderInfo);
        if (orderInfo.getOrderStatus() != 0) {
            // 订单不是待支付状态
            log.error("!order---------------------------------------------------------pay");
            return  new ResponseVO(PaymentEnum.getStatusByCode(2),2,PaymentEnum.getMsgByCode(2));
        }


        //通知PI完成交易
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("txid", completeVO.getTxId());

        Map<String, String> heads = new HashMap<>();
        heads.put("Content-Type", "application/json;charset=UTF-8");
        heads.put("Authorization", "Key " + commonConfig.getServerAccessKey());
        log.error("pi-----------------------------------------"+jsonObject);
        try {
            HttpResponse response = HttpRequest.post("https://api.minepi.com/v2/payments/" + paymentId + "/complete")
                    .headerMap(heads, false)
                    .body(String.valueOf(jsonObject))
                    .timeout(5 * 60 * 1000)
                    .execute();
            String body = response.body();
            JSONObject jsonObject1 = JSON.parseObject(body);
            String error = jsonObject1.getString("error");
            if (!StringUtils.isEmpty(error)) {
                log.error("!strinutils-----------------------------"+body);
                throw new RuntimeException("订单完成异常!");
            }
            orderInfo.setOrderStatus(1);
            // 更新订单
            orderInfoMapper.updateById(orderInfo);
            log.error("支付成功------------------------------------------------------");
            // 支付成功
            return  new ResponseVO(PaymentEnum.getStatusByCode(6),6,PaymentEnum.getMsgByCode(6));
        } catch (Exception e) {
            throw e;
        }

    }

注意,如果用户有未处理的订单,会导致用户重新创建支付失败,需要有个接口去处理未完成的订单

前端一初始化就去执行处理未完成的订单

路由到后端的接口 

 

 后端接口代码

@Override
    public ResponseVO incomplete(IncompleteVO incompleteVO) {
        log.error("incomplete--------------------------------");
        try {
            //先处理未完成的订单
            String oldpaymentId = incompleteVO.getIdentifier();
            TransactionVO transaction = incompleteVO.getTransaction();
            log.error("?transation--------------------"+transaction);
            log.error("?oldpaymentId------------------"+oldpaymentId);
            if (null != transaction) {
                log.error("transation--------------------"+transaction);
                log.error("oldpaymentId------------------"+oldpaymentId);
                String txid = transaction.getTxid();
                String txURL = transaction.get_link();

//                OrderInfo orderInfo = orderInfoMapper.selectOne(new QueryWrapper<OrderInfo>().eq("order_id", oldpaymentId));
//                if (null == orderInfo) {
//                    log.error("order-----------------null");
//                    throw new RuntimeException("旧订单不存在");
//                }
//
//                if (orderInfo.getOrderStatus()==1) {
//                    log.error("orderStatus---------------------"+orderInfo.getOrderStatus());
//                    throw new RuntimeException("订单是已支付状态");
//                }

                String get = HttpClientUtil.sendGet(txURL);
                JSONObject jsonObject1 = JSON.parseObject(get);
                String piOrderId = jsonObject1.getString("memo");//我方订单ID

                log.error("memo---------------------"+piOrderId);

                JSONObject jsonObject = new JSONObject();
                jsonObject.put("txid", txid);

                Map<String, String> heads = new HashMap<>();
                heads.put("Content-Type", "application/json;charset=UTF-8");
                heads.put("Authorization", "Key " + commonConfig.getServerAccessKey());

                try {
                    HttpResponse response = HttpRequest.post("https://api.minepi.com/v2/payments/" + piOrderId + "/complete")
                            .headerMap(heads, false)
                            .body(String.valueOf(jsonObject))
                            .timeout(5 * 60 * 1000)
                            .execute();
                    String body = response.body();
                    JSONObject jsonObject2 = JSON.parseObject(body);
                    String error = jsonObject2.getString("error");
                    if (!StringUtils.isEmpty(error)) {
                        log.error("!response------------------"+error);
                        throw new RuntimeException("订单完成异常!");
                    }

                    return new ResponseVO(PaymentEnum.getStatusByCode(7),7,PaymentEnum.getMsgByCode(7));
                } catch (Exception e) {
                    throw e;
                }
            }
        } catch (Exception e) {
            return new ResponseVO(PaymentEnum.getStatusByCode(8),8,PaymentEnum.getMsgByCode(8));
        }
        return new ResponseVO(PaymentEnum.getStatusByCode(8),8,PaymentEnum.getMsgByCode(8));
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1068723.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何系列 如何使用ff4j实现功能迭代

文章目录 功能开关是什么为什么需要功能开关&#xff1f;功能流程组件业务接入端常用Api 功能开关管理端 高级面向切面 AOP审计和监控缓存微服务中使用 概念功能 Feature功能存储 FeatureStore属性 Property属性存储 PropertyStoreFF4J架构FF4J使用开关策略 FlippingStrategy功…

【赠书活动】如何让AI在企业多快好省的落地

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

优化理论笔记

目录 一、前言 二、优化问题的基本要素 三、优化问题分类 四、最优值类型 五、最优化方法分类 六、非约束优化 1、问题定义 2、优化算法 1&#xff09;一般局部搜索过程 2&#xff09;集束搜索 3&#xff09;禁忌搜索 4&#xff09;模拟退火 5&#xff09;蛙跳算法…

使用Resnet进行图像分类训练

本文仅给出最基础的baseline进行图像分类训练&#xff0c;后续可在此代码基础上对模型结构进行修改。 一、图像分类数据集 现有一份图像类别数据集&#xff0c;类别为Y和N&#xff0c;数据目录如下&#xff1a; /datasets/data/ |-- train/ | |-- Y/ | |-- N/划分训练集…

超自动化加速落地,助力运营效率和用户体验显著提升|爱分析报告

RPA、iPaaS、AI、低代码、BPM、流程挖掘等在帮助企业实现自动化的同时&#xff0c;也在构建一座座“自动化烟囱”。自动化工具尚未融为一体&#xff0c;协同价值没有得到释放。Gartner于2019年提出超自动化&#xff08;Hyperautomation&#xff09;概念&#xff0c;主要从技术组…

法律战爆发:“币安退出俄罗斯引发冲击波“

币安是全球最大的加密货币交易所之一&#xff0c;经历了几个月的艰难时期&#xff0c;面临着各种法律挑战&#xff0c;最近将其俄罗斯分公司的所有资产出售给了一家几天前才成立的公司。 这家主要交易所的麻烦始于 6 月份&#xff0c;当时美国证券交易委员会 (SEC)起…

PyTorch Lightning - LightningModule 训练逻辑 (training_step) 异常处理 try-except

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/133673820 在使用 LightningModule 框架训练模型时&#xff0c;因数据导致的训练错误&#xff0c;严重影响训练稳定性&#xff0c;因此需要使用 t…

消费者的力量:跨境电商如何满足新一代的需求

当代跨境电商行业正处于高速发展的阶段&#xff0c;而新一代消费者正在塑造这一行业的未来。他们的需求和消费行为发生了巨大变化&#xff0c;对于跨境电商来说&#xff0c;满足这一新一代消费者的需求至关重要。本文将探讨新一代消费者的需求以及跨境电商如何满足这些需求的方…

【Bond随你温故Azure Architecture】之HADR篇

上次复盘数据保护策略还是在《数据需要找回怎么办&#xff1f;我们如何选择正确的恢复/退回方式&#xff1f;》探讨了在application&DB层面上&#xff0c;不同level的数据保护有不同策略。而它也恰好是今天HA&DR版图的一角&#xff08;RDBMS部分&#xff09;&#xff0…

【机器学习】svm

参考 sklearn中SVC中的参数说明与常用函数_sklearn svc参数-CSDN博客https://blog.csdn.net/transformed/article/details/90437821 参考PYthon 教你怎么选择SVM的核函数kernel及案例分析_clfsvm.svc(kernel)-CSDN博客https://blog.csdn.net/c1z2w3456789/article/details/10…

【Python_PySide2学习笔记(十六)】多行文本框QPlainTextEdit类的的基本用法

多行文本框QPlainTextEdit类的的基本用法 前言正文1、创建多行文本框2、多行文本框获取文本3、多行文本框获取选中文本4、多行文本框设置提示5、多行文本框设置文本6、多行文本框在末尾添加文本7、多行文本框在光标处插入文本8、多行文本框清空文本9、多行文本框拷贝文本到剪贴…

什么是EJB以及和Spring Framework的区别

&#x1f454; 前言 EJB&#xff0c;对于新生代程序员来说&#xff0c;是一个既熟悉又陌生的名词&#xff0c;EJB&#xff0c;大家都听说过&#xff0c;但是不一定都了解过&#xff0c;EJB是一种开发规范&#xff0c;而不是像Spring Framework一样是一个开源框架&#xff0c;E…

卫星/RedCap/高算力/解决方案/创新金奖……移远通信为IOTE 2023再添新活力

9月20日&#xff0c;IOTE 2023第二十届国际物联网展深圳场震撼来袭。 作为IOTE多年的“老朋友”&#xff0c;移远通信在参展当天&#xff0c;不仅有5G RedCap、卫星通信、高算力、车载等高性能产品及终端展出&#xff0c;还携智慧出行、智慧生活、智慧能源、工业互联网等多领域…

redis集群是符合cap中的ap还是cp

近期忽然间考虑到了这个问题。 cap 理论 cap是实现分布式系统的思想。 由3个元素组成。 Consistency&#xff08;一致性&#xff09; 在任何对等 server 上读取的数据都是最新版&#xff0c;不会读取出旧数据。比如 zookeeper 集群&#xff0c;从任何一台节点读取出来的数据…

SpringBoot 如何配置 OAuth2 认证

在Spring Boot中配置OAuth2认证 OAuth2是一种用于授权的开放标准&#xff0c;允许应用程序安全地访问用户的资源。Spring Boot提供了强大的支持&#xff0c;使得在应用程序中配置OAuth2认证变得非常容易。本文将介绍如何在Spring Boot中配置OAuth2认证&#xff0c;以便您可以在…

ThreeJS-3D教学六-物体位移旋转

之前文章其实也有涉及到这方面的内容&#xff0c;比如在ThreeJS-3D教学三&#xff1a;平移缩放物体沿轨迹运动这篇中&#xff0c;通过获取轨迹点物体动起来&#xff0c;其它几篇文章也有旋转的效果&#xff0c;本篇我们来详细看下&#xff0c;另外加了tween.js知识点&#xff0…

基于SpringBoot的靓车汽车销售网站

目录 前言 一、技术栈 二、系统功能介绍 用户信息管理 车辆展示管理 车辆品牌管理 用户交流管理 购物车 用户交流 我的订单管理 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的…

除静电离子风刀的工作原理及应用

除静电离子风刀是一种能够产生高速气流并带有离子的设备&#xff0c;主要用于去除物体表面的静电。它的工作原理是通过离子产生器产生大量负离子&#xff0c;并通过高压电场将离子加速&#xff0c;使其成为一股高速气流&#xff0c;从而将静电荷从物体表面中除去。 除静电离子…

阿里云 linux tomcat 无法访问方法

1、阿里云放行tomcat端口 例如7077端口号 2、linux 命令行防火墙 设置端口打开 以下命令查看是否开启指定端口 firewall-cmd --list-ports以下命令添加指定端口让防火墙放行 firewall-cmd --zonepublic --add-port3306/tcp --permanent以下命令重新启动防火墙 systemctl re…

聊一下读完“优势成长”这本书后感

&#xff08;优势成长上&#xff09; (优势成长下) 最近读完了一本个人觉得还可以的书,这本书是一位新东方老师,帅键翔老师写的 整本书概括起来,最重要一点就是找到自己的优势,然后利用自己的优势,去挖掘自己的潜力,发现新大陆 能适应时代变化的&#xff0c;是“新木桶原理”&a…