springboot整合pi支付开发

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密钥

java 复制代码
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中心来的用户信息

java 复制代码
import lombok.Data;

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

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

paymentVO接收支付授权的信息

java 复制代码
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接收支付完成的信息

java 复制代码
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接收未完成订单的信息

java 复制代码
/**
 * @author ThinkPad
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class IncompleteVO {

    private String identifier;


    private TransactionVO transaction;
}
java 复制代码
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

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


    private String txid;


    private String _link;
}

工具类

发起http请求工具类

java 复制代码
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;
    }

}

分布式锁工具类

java 复制代码
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工具类

java 复制代码
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;
    }

}

信息返回的枚举

java 复制代码
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层

java 复制代码
 /**
     * 处理未完成的订单 (这部十分重要,会影响到后面的操作)
     */
    @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层

java 复制代码
    /**
     * 请求支付授权,创建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));
    }

前端代码

前端路由

javascript 复制代码
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,
  })
}

支付前端代码

javascript 复制代码
 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)}
      });
    },

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

javascript 复制代码
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));
    }
相关推荐
Java中文社群1 分钟前
必看!导致事务失效的7大典型场景!
java·后端·面试
_祝你今天愉快5 分钟前
HashMap 底层原理 (JDK 1.8 源码分析)
android·java·后端
七七软件开发9 分钟前
直播 app 系统架构分析
java·python·小程序·系统架构·php
曲莫终13 分钟前
JAVA原生Servlet支持SSE
前端·后端
程序员陆通15 分钟前
Spring Cloud微服务中的内存泄漏问题定位与解决方案
java·spring cloud·微服务
bcbnb16 分钟前
iOS混淆工具有哪些?技术演进与选型趋势全景解析
后端
冒泡的肥皂16 分钟前
2PL-事务并发遇到的问题(一
数据库·后端·mysql
极光雨雨18 分钟前
JVM中年轻代、老年代、永久代(或元空间)、Eden区和Survivor区概念介绍
java·jvm
盖世英雄酱5813630 分钟前
配置的那点玄学
java·后端
Ice__Cai32 分钟前
Django 性能优化详解:从数据库到缓存,打造高效 Web 应用
数据库·后端·python·缓存·性能优化·django