• springboot整合pi支付开发


    pi支付流程图

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

    支付流程

     引入依赖

    1. <dependency>
    2. <groupId>com.squareup.okhttp3</groupId>
    3. <artifactId>okhttp</artifactId>
    4. <version>4.10.0-RC1</version>
    5. </dependency>
    6. <dependency>
    7. <groupId>cn.hutool</groupId>
    8. <artifactId>hutool-all</artifactId>
    9. <version>5.8.0.M4</version>
    10. </dependency>
    11. <dependency>
    12. <groupId>org.apache.httpcomponents</groupId>
    13. <artifactId>httpclient</artifactId>
    14. <version>4.5.13</version>
    15. </dependency>
    16. <dependency>
    17. <groupId>commons-lang</groupId>
    18. <artifactId>commons-lang</artifactId>
    19. <version>2.6</version>
    20. </dependency>

    配置api密钥

    1. import lombok.Data;
    2. import org.springframework.beans.factory.annotation.Value;
    3. import org.springframework.context.annotation.Configuration;
    4. /**
    5. * 服务器端key
    6. * @author ThinkPad
    7. */
    8. @Configuration
    9. @Data
    10. public class CommonConfig {
    11. @Value("${sdk.serverAccessKey}")
    12. private String serverAccessKey;
    13. }

     接收和返回数据对象

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

    1. import lombok.Data;
    2. /**
    3. * @author wzx
    4. * 登录数据封装类
    5. */
    6. @Data
    7. public class LoginVO {
    8. private String userId;
    9. private String userName;
    10. private String accessToken;
    11. }

    paymentVO接收支付授权的信息

    1. import lombok.AllArgsConstructor;
    2. import lombok.Data;
    3. import lombok.NoArgsConstructor;
    4. import java.math.BigDecimal;
    5. /**
    6. * @author ThinkPad
    7. */
    8. @Data
    9. @AllArgsConstructor
    10. @NoArgsConstructor
    11. public class PaymentVO {
    12. private String paymentId;
    13. // 交易金额
    14. private BigDecimal amount;
    15. // 名片对应的用户数据
    16. private String shopUserId;
    17. // 商品id
    18. private String shopId;
    19. // 当前账号用户的id
    20. private String userId;
    21. }

    completeVO接收支付完成的信息

    1. import lombok.AllArgsConstructor;
    2. import lombok.Data;
    3. import lombok.NoArgsConstructor;
    4. /**
    5. * @author ThinkPad
    6. */
    7. @Data
    8. @NoArgsConstructor
    9. @AllArgsConstructor
    10. public class CompleteVO {
    11. // PI支付ID
    12. private String paymentId;
    13. // txId
    14. private String txId;
    15. // 订单ID【余额支付参数】
    16. private String orderId;
    17. // 支付方式:0:PI钱包 1:余额支付
    18. private String payType;
    19. }

    incompleteVO接收未完成订单的信息

    1. /**
    2. * @author ThinkPad
    3. */
    4. @Data
    5. @NoArgsConstructor
    6. @AllArgsConstructor
    7. public class IncompleteVO {
    8. private String identifier;
    9. private TransactionVO transaction;
    10. }
    1. import lombok.AllArgsConstructor;
    2. import lombok.Data;
    3. import lombok.NoArgsConstructor;
    4. /**
    5. * @author ThinkPad
    6. */
    7. @Data
    8. @NoArgsConstructor
    9. @AllArgsConstructor
    10. public class TransactionVO {
    11. private String txid;
    12. private String _link;
    13. }

    工具类

    发起http请求工具

    1. import org.apache.commons.lang.StringUtils;
    2. import org.apache.http.HttpEntity;
    3. import org.apache.http.ParseException;
    4. import org.apache.http.client.methods.CloseableHttpResponse;
    5. import org.apache.http.client.methods.HttpGet;
    6. import org.apache.http.client.methods.HttpPost;
    7. import org.apache.http.entity.StringEntity;
    8. import org.apache.http.impl.client.CloseableHttpClient;
    9. import org.apache.http.impl.client.HttpClients;
    10. import org.apache.http.util.EntityUtils;
    11. import java.io.BufferedReader;
    12. import java.io.IOException;
    13. import java.io.InputStream;
    14. import java.io.InputStreamReader;
    15. import java.net.HttpURLConnection;
    16. import java.net.URL;
    17. /**
    18. * @author Ashy.Cheung
    19. * @http 请求工具类
    20. * @date 2017.11.10
    21. */
    22. public class HttpClientUtil {
    23. public static String sendGet(String url) {
    24. CloseableHttpClient httpclient = HttpClients.createDefault();
    25. HttpGet httpget = new HttpGet(url);
    26. CloseableHttpResponse response = null;
    27. try {
    28. response = httpclient.execute(httpget);
    29. } catch (IOException e1) {
    30. e1.printStackTrace();
    31. }
    32. String result = null;
    33. try {
    34. HttpEntity entity = response.getEntity();
    35. if (entity != null) {
    36. result = EntityUtils.toString(entity);
    37. }
    38. } catch (ParseException | IOException e) {
    39. e.printStackTrace();
    40. } finally {
    41. try {
    42. response.close();
    43. } catch (IOException e) {
    44. e.printStackTrace();
    45. }
    46. }
    47. return result;
    48. }
    49. /**
    50. *
    51. * @param url
    52. * @param charsetName 返回字符集
    53. * @return
    54. */
    55. public static String sendGet(String url, String charsetName) {
    56. InputStream inputStream = null;
    57. HttpURLConnection urlConnection = null;
    58. try {
    59. URL url1 = new URL(url);
    60. urlConnection = (HttpURLConnection) url1.openConnection();
    61. // 将返回的输入流转换成字符串
    62. inputStream = urlConnection.getInputStream();
    63. // 指定编码格式
    64. if (StringUtils.isBlank(charsetName)) {
    65. charsetName = "UTF-8";
    66. }
    67. InputStreamReader inputStreamReader = new InputStreamReader(inputStream, charsetName);
    68. BufferedReader in = new BufferedReader(inputStreamReader);
    69. String jsonUserStr = in.readLine();
    70. return jsonUserStr;
    71. } catch (Exception e) {
    72. throw new RuntimeException(e);
    73. } finally {
    74. try {
    75. if (null != inputStream) {
    76. inputStream.close();
    77. }
    78. urlConnection.disconnect();
    79. } catch (Exception e) {
    80. }
    81. try {
    82. if (null != urlConnection) {
    83. urlConnection.disconnect();
    84. }
    85. } catch (Exception e) {
    86. }
    87. }
    88. }
    89. /**
    90. * 发送HttpPost请求,参数为String
    91. * 接收端以流形式接收
    92. */
    93. public static String sendPost(String url, String param) {
    94. CloseableHttpClient httpclient = HttpClients.createDefault();
    95. StringEntity strEntity = null;
    96. try {
    97. strEntity = new StringEntity(param, "UTF-8");
    98. strEntity.setContentType("application/json");
    99. } catch (Exception e1) {
    100. e1.printStackTrace();
    101. }
    102. HttpPost httppost = new HttpPost(url);
    103. httppost.setEntity(strEntity);
    104. CloseableHttpResponse response = null;
    105. String result = null;
    106. try {
    107. response = httpclient.execute(httppost);
    108. HttpEntity entity1 = response.getEntity();
    109. result = EntityUtils.toString(entity1);
    110. } catch (IOException e) {
    111. // e.printStackTrace();
    112. } finally {
    113. try {
    114. response.close();
    115. } catch (Exception e) {
    116. }
    117. }
    118. return result;
    119. }
    120. /**
    121. * 发送不带参数的HttpPost请求
    122. */
    123. public static String sendPost(String url) {
    124. CloseableHttpClient httpclient = HttpClients.createDefault();
    125. HttpPost httppost = new HttpPost(url);
    126. CloseableHttpResponse response = null;
    127. try {
    128. response = httpclient.execute(httppost);
    129. } catch (IOException e) {
    130. e.printStackTrace();
    131. }
    132. HttpEntity entity = response.getEntity();
    133. String result = null;
    134. try {
    135. result = EntityUtils.toString(entity);
    136. } catch (ParseException | IOException e) {
    137. e.printStackTrace();
    138. } finally {
    139. try {
    140. response.close();
    141. } catch (Exception e) {
    142. }
    143. }
    144. return result;
    145. }
    146. }

     分布式锁工具类

    1. import org.slf4j.Logger;
    2. import org.slf4j.LoggerFactory;
    3. import org.springframework.data.redis.connection.RedisStringCommands;
    4. import org.springframework.data.redis.connection.ReturnType;
    5. import org.springframework.data.redis.core.RedisCallback;
    6. import org.springframework.data.redis.core.RedisTemplate;
    7. import org.springframework.data.redis.core.types.Expiration;
    8. import org.springframework.stereotype.Component;
    9. import javax.annotation.Resource;
    10. import java.nio.charset.StandardCharsets;
    11. @Component
    12. public class RedisLockUtil {
    13. private static final Logger log = LoggerFactory.getLogger(RedisLockUtil.class);
    14. @Resource
    15. RedisTemplate redisTemplate;
    16. /**
    17. * 释放锁脚本,原子操作,lua脚本
    18. */
    19. private static final String UNLOCK_LUA;
    20. static {
    21. StringBuilder sb = new StringBuilder();
    22. sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
    23. sb.append("then ");
    24. sb.append(" return redis.call(\"del\",KEYS[1]) ");
    25. sb.append("else ");
    26. sb.append(" return 0 ");
    27. sb.append("end ");
    28. UNLOCK_LUA = sb.toString();
    29. }
    30. /**
    31. * 获取分布式锁,原子操作
    32. *
    33. * @param lockKey 锁
    34. * @param lockValue 唯一ID
    35. * @param leaseTime 过期时间 秒
    36. * @return 是否枷锁成功
    37. */
    38. public boolean tryLock(String lockKey, String lockValue, long leaseTime) {
    39. try {
    40. RedisCallback callback = (connection) -> connection.set(lockKey.getBytes(StandardCharsets.UTF_8),
    41. lockValue.getBytes(StandardCharsets.UTF_8), Expiration.seconds(leaseTime),
    42. RedisStringCommands.SetOption.SET_IF_ABSENT);
    43. return redisTemplate.execute(callback);
    44. } catch (Exception e) {
    45. log.error("redis lock error ,lock key: {}, value : {}, error info : {}", lockKey, lockValue, e);
    46. }
    47. return false;
    48. }
    49. /**
    50. * 释放锁
    51. *
    52. * @param lockKey 锁
    53. * @param lockValue 唯一ID
    54. * @return 执行结果
    55. */
    56. public boolean unlock(String lockKey, String lockValue) {
    57. RedisCallback callback = (connection) -> connection.eval(UNLOCK_LUA.getBytes(), ReturnType.BOOLEAN, 1, lockKey.getBytes(StandardCharsets.UTF_8), lockValue.getBytes(StandardCharsets.UTF_8));
    58. return redisTemplate.execute(callback);
    59. }
    60. /**
    61. * 获取分布式锁,该方法不再使用
    62. *
    63. * @param lockKey 锁
    64. * @param lockValue 唯一ID
    65. * @param waitTime 等待时间 秒
    66. * @param leaseTime 过期时间 秒
    67. * @return 是否枷锁成功
    68. */
    69. @Deprecated
    70. public boolean tryLock(String lockKey, String lockValue, long waitTime, long leaseTime) {
    71. try {
    72. RedisCallback callback = (connection) -> connection.set(lockKey.getBytes(StandardCharsets.UTF_8),
    73. lockValue.getBytes(StandardCharsets.UTF_8), Expiration.seconds(leaseTime),
    74. RedisStringCommands.SetOption.SET_IF_ABSENT);
    75. return redisTemplate.execute(callback);
    76. } catch (Exception e) {
    77. log.error("redis lock error ,lock key: {}, value : {}, error info : {}", lockKey, lockValue, e);
    78. }
    79. return false;
    80. }
    81. }

    生成uuid工具类

    1. import java.text.SimpleDateFormat;
    2. import java.util.Date;
    3. public class UUID {
    4. public static String randomUUID() {
    5. SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HH'H'mm'M'ss'S'SSS");
    6. String id = sdf.format(new Date()) + (int) ((Math.random() * 9 + 1) * 100000000) + (int) ((Math.random() * 9 + 1) * 10);
    7. return id;
    8. }
    9. public static String randomQr() {
    10. String id = (int) ((Math.random() * 9 + 1) * 1000) + "-" + (int) ((Math.random() * 9 + 1) * 1000) + "-" + (int) ((Math.random() * 9 + 1) * 1000);
    11. return id;
    12. }
    13. }

    信息返回的枚举

    1. import lombok.AllArgsConstructor;
    2. import lombok.Getter;
    3. /**
    4. * @author ThinkPad
    5. */
    6. @Getter
    7. @AllArgsConstructor
    8. public enum PaymentEnum {
    9. PAYMENT_ENUM_1(1, "订单不存在","失败"),
    10. PAYMENT_ENUM_2(2,"订单不是待支付状态","失败"),
    11. PAYMENT_ENUM_3(3,"支付金额少于订单金额","失败"),
    12. PAYMENT_ENUM_4(4,"调用太快","失败"),
    13. PAYMENT_ENUM_5(5,"余额不足,请前往充值","失败"),
    14. PAYMENT_ENUM_6(6,"支付成功","成功"),
    15. PAYMENT_ENUM_7(7,"处理成功","失败"),
    16. PAYMENT_ENUM_8(8,"处理失败","失败");
    17. private final Integer code;
    18. private final String msg;
    19. private final String status;
    20. public static String getMsgByCode(Integer code) {
    21. for (PaymentEnum value : PaymentEnum.values()) {
    22. if (value.getCode().equals(code)) {
    23. return value.getMsg();
    24. }
    25. }
    26. return null;
    27. }
    28. public static String getStatusByCode(Integer code) {
    29. for (PaymentEnum value : PaymentEnum.values()) {
    30. if (value.getCode().equals(code)) {
    31. return value.getStatus() ;
    32. }
    33. }
    34. return null;
    35. }
    36. }

    支付的controller层

    1. /**
    2. * 处理未完成的订单 (这部十分重要,会影响到后面的操作)
    3. */
    4. @PostMapping("payOrder/incomplete")
    5. @ApiOperation("处理未完成的订单")
    6. @ApiImplicitParam(name = "Authorization", value = "传入你的令牌",required = true, dataType = "String",paramType="header")
    7. public ResponseVO incomplete(@RequestBody IncompleteVO incompleteVO) {
    8. try {
    9. return orderInfoService.incomplete(incompleteVO);
    10. } catch (Exception e) {
    11. log.error("报错如下:{}", e.getMessage());
    12. throw new BusinessException("支付失败,请联系后台人员"+e.getLocalizedMessage()+e.toString()+e.getCause().toString());
    13. }
    14. }
    15. /**
    16. * 前端请求支付授权,在本地订单创建后调
    17. */
    18. @PostMapping("payOrder/approve")
    19. @ApiOperation("前端请求支付授权,在本地订单创建后调")
    20. @ApiImplicitParam(name = "Authorization", value = "传入你的令牌",required = true, dataType = "String",paramType="header")
    21. public ResponseVO approve(@RequestBody PaymentVO paymentVO) {
    22. try {
    23. String orderId = orderInfoService.approve(paymentVO);
    24. return ResponseVO.getSuccessResponseVo(orderId);
    25. } catch (Exception e) {
    26. log.error("报错如下:{}", e.getMessage());
    27. throw new BusinessException("支付失败,请联系后台人员"+e.getLocalizedMessage()+e.toString()+e.getCause().toString());
    28. }
    29. }
    30. /**
    31. * 前端支付完成,余额支付直接调用此方法
    32. */
    33. @PostMapping("payOrder/complete")
    34. @ApiOperation("前端支付完成,余额支付直接调用此方法")
    35. @ApiImplicitParam(name = "Authorization", value = "传入你的令牌",required = true, dataType = "String",paramType="header")
    36. public ResponseVO complete(@RequestBody CompleteVO completeVO) {
    37. try {
    38. return orderInfoService.complete(completeVO);
    39. } catch (Exception e) {
    40. log.error("报错如下:{}", e.getMessage());
    41. throw new BusinessException("支付失败,请联系后台人员"+e.getLocalizedMessage()+e.toString()+e.getCause().toString());
    42. }
    43. }
    44. /**
    45. * 取消支付,订单关闭
    46. */
    47. @PostMapping("payOrder/cancelled")
    48. @ApiOperation("取消支付,订单关闭")
    49. @ApiImplicitParam(name = "Authorization", value = "传入你的令牌",required = true, dataType = "String",paramType="header")
    50. public ResponseVO cancelled(@RequestBody String orderId) {
    51. try {
    52. Boolean order = orderInfoService.cancelled(orderId);
    53. // if (!order){throw new BusinessException("取消订单失败");}
    54. return ResponseVO.getSuccessResponseVo("取消订单成功");
    55. } catch (Exception e) {
    56. log.error("报错如下:{}", e.getMessage());
    57. throw new BusinessException("取消失败,请联系后台人员"+e.getLocalizedMessage()+e.toString()+e.getCause().toString());
    58. }
    59. }

    支付的service层

    1. /**
    2. * 请求支付授权,创建order。返回orderId
    3. * @param paymentVO
    4. * @return
    5. */
    6. @Override
    7. @Transactional(rollbackFor = Exception.class)
    8. public String approve(PaymentVO paymentVO) {
    9. log.error("approve-------------------------------------------------------------");
    10. OrderInfo orderInfo;
    11. log.error("paymentVO----------------------------------"+paymentVO);
    12. //获取付款信息
    13. OkHttpClient client = new OkHttpClient();
    14. Request request = new Request.Builder()
    15. .url("https://api.minepi.com/v2/payments/" + paymentVO.getPaymentId())
    16. .addHeader("Authorization", "Key " + commonConfig.getServerAccessKey())
    17. .build();
    18. try (Response response = client.newCall(request).execute()) {
    19. if (!response.isSuccessful()) {
    20. String string = response.body().string();
    21. JSONObject jsonObject1 = JSON.parseObject(string);
    22. log.error("!response-------------------------------------------------------------"+commonConfig.getServerAccessKey());
    23. throw new RuntimeException("payments error " + jsonObject1.getString("error_message"));
    24. }
    25. String string = response.body().string();
    26. log.error("response-------------------------------------------------------------"+string);
    27. JSONObject jsonObject1 = JSON.parseObject(string);
    28. //校验实际支付金额
    29. BigDecimal userFinalPrice = paymentVO.getAmount();
    30. if (userFinalPrice.compareTo(jsonObject1.getBigDecimal("amount")) < 0) {
    31. log.error(userFinalPrice+"response-------------------------------------------------------------"+jsonObject1.getBigDecimal("amount"));
    32. throw new RuntimeException("支付金额少于订单金额");
    33. }
    34. } catch (Exception e) {
    35. throw new BusinessException("支付失败,请联系后台人员"+e.getLocalizedMessage()+e.toString()+e.getCause().toString());
    36. }
    37. OkHttpClient client1 = new OkHttpClient();
    38. //信息真实,通知PI我准备好了,可以付款了
    39. Request request1 = new Request.Builder()
    40. .url("https://api.minepi.com/v2/payments/" + paymentVO.getPaymentId() + "/approve")
    41. .addHeader("Content-Type", "application/json")
    42. .addHeader("Access-Control-Allow-Origin", "*")
    43. .addHeader("Authorization", "Key " + commonConfig.getServerAccessKey())
    44. .post(RequestBody.create("", MediaType.parse("application/json")))
    45. .build();
    46. try (Response response1 = client1.newCall(request1).execute()) {
    47. if (!response1.isSuccessful()) {
    48. throw new RuntimeException("approve error: ");
    49. }
    50. log.error("response1-------------------------------------------------------------");
    51. //更新支付报文
    52. // tMerStoreGoodsOrderEntity.setPayOrderId(paymentDto.getPaymentId());
    53. // tMerStoreGoodsOrderEntity.setPayStatusType("10007002");//支付中
    54. // itMerStoreGoodsOrderService.updateById(tMerStoreGoodsOrderEntity);
    55. log.error("return-------------------------------------------------------------");
    56. } catch (RuntimeException | IOException e) {
    57. log.error("error-------------------------------------------------------------");
    58. e.printStackTrace();
    59. }
    60. // 生成订单
    61. orderInfo = new OrderInfo();
    62. orderInfo.setOrderId(StringUtil.generateShortId());
    63. orderInfo.setShopId(paymentVO.getShopId());
    64. orderInfo.setUserId(paymentVO.getUserId());
    65. orderInfo.setShopUserId(paymentVO.getShopUserId());
    66. orderInfo.setAmount(paymentVO.getAmount());
    67. orderInfoMapper.insert(orderInfo);
    68. log.error("生成订单-------------------------------------------------------------");
    69. return orderInfo.getOrderId();
    70. }
    71. /**
    72. * 前端支付完成,余额支付直接调用此方法
    73. */
    74. @Override
    75. @Transactional(rollbackFor = Exception.class)
    76. public ResponseVO complete(CompleteVO completeVO) {
    77. String payType = completeVO.getPayType();
    78. log.error("complete------------------------------------------------------------"+completeVO);
    79. if ("1".equals(payType)) {
    80. //余额支付
    81. String orderId = completeVO.getOrderId();
    82. String lockName = "access:lock:complete:" + orderId;
    83. String lockId = UUID.randomUUID();
    84. if (!redisLockUtil.tryLock(lockName, lockId, 20L)) {
    85. // 调用太快
    86. return new ResponseVO(PaymentEnum.getStatusByCode(4),4,PaymentEnum.getMsgByCode(4));
    87. }
    88. // 获取订单信息
    89. OrderInfo orderInfo = orderInfoMapper.selectOne(new QueryWrapper().eq("order_id", orderId));
    90. if ((orderInfo.getOrderStatus() != 0)) {
    91. // 订单不是待支付状态
    92. return new ResponseVO(PaymentEnum.getStatusByCode(2),2,PaymentEnum.getMsgByCode(2));
    93. }
    94. String userId = orderInfo.getUserId();
    95. AccountInfo accountInfo = accountInfoMapper.selectOne(new QueryWrapper()
    96. .eq("user_id", userId));
    97. BigDecimal balance = accountInfo.getPiBalance();
    98. if (balance.compareTo(orderInfo.getAmount()) < 0) {
    99. // 余额不足,请前往充值
    100. return new ResponseVO(PaymentEnum.getStatusByCode(5),5,PaymentEnum.getMsgByCode(5));
    101. }
    102. int update = orderInfoMapper.update(null,new UpdateWrapper()
    103. .eq("order_id",orderId)
    104. .set("order_status",1));
    105. balance=balance.subtract(orderInfo.getAmount());
    106. int update1 = accountInfoMapper.update(null, new UpdateWrapper()
    107. .eq("user_id", userId)
    108. .set("pi_balance", balance));
    109. // 支付成功
    110. return new ResponseVO(PaymentEnum.getStatusByCode(6),6,PaymentEnum.getMsgByCode(6));
    111. }
    112. //PI钱包支付
    113. String paymentId = completeVO.getPaymentId();//PI订单号
    114. String lockName = "access:lock:complete:" + paymentId;
    115. String lockId = UUID.randomUUID();
    116. log.error(paymentId+"-----------------"+lockName+"---------------------"+lockId);
    117. if (!redisLockUtil.tryLock(lockName, lockId, 20L)) {
    118. // 调用太快
    119. log.error("!RedisLockUtil---------------------------------------------------------调用太快");
    120. return new ResponseVO(PaymentEnum.getStatusByCode(4),4,PaymentEnum.getMsgByCode(4));
    121. }
    122. OrderInfo orderInfo = orderInfoMapper.selectOne(new QueryWrapper()
    123. .eq("order_id", completeVO.getOrderId()));
    124. log.error("orderId--------------------------------------------------------------"+orderInfo);
    125. if (null == orderInfo) {
    126. // 订单不存在
    127. log.error("!orderinfo--------------------------------------------------------不存在");
    128. return new ResponseVO(PaymentEnum.getStatusByCode(1),1,PaymentEnum.getMsgByCode(1));
    129. }
    130. log.error("orderinfo------------------------------------------------------------------"+orderInfo);
    131. if (orderInfo.getOrderStatus() != 0) {
    132. // 订单不是待支付状态
    133. log.error("!order---------------------------------------------------------pay");
    134. return new ResponseVO(PaymentEnum.getStatusByCode(2),2,PaymentEnum.getMsgByCode(2));
    135. }
    136. //通知PI完成交易
    137. JSONObject jsonObject = new JSONObject();
    138. jsonObject.put("txid", completeVO.getTxId());
    139. Map heads = new HashMap<>();
    140. heads.put("Content-Type", "application/json;charset=UTF-8");
    141. heads.put("Authorization", "Key " + commonConfig.getServerAccessKey());
    142. log.error("pi-----------------------------------------"+jsonObject);
    143. try {
    144. HttpResponse response = HttpRequest.post("https://api.minepi.com/v2/payments/" + paymentId + "/complete")
    145. .headerMap(heads, false)
    146. .body(String.valueOf(jsonObject))
    147. .timeout(5 * 60 * 1000)
    148. .execute();
    149. String body = response.body();
    150. JSONObject jsonObject1 = JSON.parseObject(body);
    151. String error = jsonObject1.getString("error");
    152. if (!StringUtils.isEmpty(error)) {
    153. log.error("!strinutils-----------------------------"+body);
    154. throw new RuntimeException("订单完成异常!");
    155. }
    156. orderInfo.setOrderStatus(1);
    157. // 更新订单
    158. orderInfoMapper.updateById(orderInfo);
    159. log.error("支付成功------------------------------------------------------");
    160. // 支付成功
    161. return new ResponseVO(PaymentEnum.getStatusByCode(6),6,PaymentEnum.getMsgByCode(6));
    162. } catch (Exception e) {
    163. throw e;
    164. }
    165. }
    166. @Override
    167. @Transactional(rollbackFor = Exception.class)
    168. public Boolean cancelled(String orderId) {
    169. int update = orderInfoMapper.update(null, new UpdateWrapper()
    170. .eq("order_id", orderId)
    171. .set("order_status", 3));
    172. return update > 0;
    173. }
    174. @Override
    175. public ResponseVO incomplete(IncompleteVO incompleteVO) {
    176. log.error("incomplete--------------------------------");
    177. try {
    178. //先处理未完成的订单
    179. String oldpaymentId = incompleteVO.getIdentifier();
    180. TransactionVO transaction = incompleteVO.getTransaction();
    181. log.error("?transation--------------------"+transaction);
    182. log.error("?oldpaymentId------------------"+oldpaymentId);
    183. if (null != transaction) {
    184. log.error("transation--------------------"+transaction);
    185. log.error("oldpaymentId------------------"+oldpaymentId);
    186. String txid = transaction.getTxid();
    187. String txURL = transaction.get_link();
    188. // OrderInfo orderInfo = orderInfoMapper.selectOne(new QueryWrapper().eq("order_id", oldpaymentId));
    189. // if (null == orderInfo) {
    190. // log.error("order-----------------null");
    191. // throw new RuntimeException("旧订单不存在");
    192. // }
    193. //
    194. // if (orderInfo.getOrderStatus()==1) {
    195. // log.error("orderStatus---------------------"+orderInfo.getOrderStatus());
    196. // throw new RuntimeException("订单是已支付状态");
    197. // }
    198. String get = HttpClientUtil.sendGet(txURL);
    199. JSONObject jsonObject1 = JSON.parseObject(get);
    200. String piOrderId = jsonObject1.getString("memo");//我方订单ID
    201. log.error("memo---------------------"+piOrderId);
    202. JSONObject jsonObject = new JSONObject();
    203. jsonObject.put("txid", txid);
    204. Map heads = new HashMap<>();
    205. heads.put("Content-Type", "application/json;charset=UTF-8");
    206. heads.put("Authorization", "Key " + commonConfig.getServerAccessKey());
    207. try {
    208. HttpResponse response = HttpRequest.post("https://api.minepi.com/v2/payments/" + piOrderId + "/complete")
    209. .headerMap(heads, false)
    210. .body(String.valueOf(jsonObject))
    211. .timeout(5 * 60 * 1000)
    212. .execute();
    213. String body = response.body();
    214. JSONObject jsonObject2 = JSON.parseObject(body);
    215. String error = jsonObject2.getString("error");
    216. if (!StringUtils.isEmpty(error)) {
    217. log.error("!response------------------"+error);
    218. throw new RuntimeException("订单完成异常!");
    219. }
    220. return new ResponseVO(PaymentEnum.getStatusByCode(7),7,PaymentEnum.getMsgByCode(7));
    221. } catch (Exception e) {
    222. throw e;
    223. }
    224. }
    225. } catch (Exception e) {
    226. return new ResponseVO(PaymentEnum.getStatusByCode(8),8,PaymentEnum.getMsgByCode(8));
    227. }
    228. return new ResponseVO(PaymentEnum.getStatusByCode(8),8,PaymentEnum.getMsgByCode(8));
    229. }

     前端代码

    前端路由 

    1. API
    2. // 授权
    3. import request from "@/api/http";
    4. import axios from "axios";
    5. // 支付授权
    6. export function payAuth(data){
    7. return request({
    8. url: "/api/order/payOrder/approve",
    9. method: "post",
    10. data,
    11. });
    12. }
    13. //未完成订单
    14. export function payIncomplete(data){
    15. return request({
    16. url: "/api/order/payOrder/incomplete",
    17. method: "post",
    18. data,
    19. });
    20. }
    21. //支付成功
    22. export function payDone(data){
    23. return request({
    24. url: "/api/order/payOrder/complete",
    25. method: "post",
    26. data,
    27. });
    28. }
    29. //支付取消
    30. export function payCancel(data){
    31. return request({
    32. url: "/api/order/payOrder/cancelled",
    33. method: "post",
    34. data,
    35. })
    36. }

    支付前端代码

    1. test(){
    2. const pay_message = this.pay_message
    3. let orderid = ''
    4. console.log({
    5. amount: pay_message.amount,
    6. shopUserId: pay_message.shopUserId,
    7. shopId: pay_message.shopId,
    8. userId: pay_message.userId
    9. })
    10. Pi.createPayment({
    11. // Amount of π to be paid:
    12. amount: 3.14,
    13. // An explanation of the payment - will be shown to the user:
    14. memo: "购买特殊数据", // e.g: "Digital kitten #1234",
    15. // An arbitrary developer-provided metadata object - for your own usage:
    16. metadata: { productID : 'apple_pie_1' }, // e.g: { kittenId: 1234 }
    17. }, {
    18. // Callbacks you need to implement - read more about those in the detailed docs linked below:
    19. // 授权
    20. async onReadyForServerApproval(paymentId) {
    21. console.log('paymentId',paymentId)
    22. payAuth({
    23. paymentId: paymentId,
    24. amount: pay_message.amount,
    25. shopUserId: pay_message.shopUserId,
    26. shopId: pay_message.shopId,
    27. userId: pay_message.userId
    28. }).then((data) => {
    29. orderid = data.data
    30. console.log('orderId',orderid)
    31. }).catch(error => {
    32. console.log(error)
    33. })
    34. },
    35. //支付成功
    36. onReadyForServerCompletion: function(paymentId, txid) {
    37. alert(1111)
    38. console.log(paymentId, 'paymentId', 'txid', txid,'orderid',orderid )
    39. payDone({paymentId: paymentId, txId: txid, orderId: orderid,payType:'0'}).then(res => {
    40. console.log(res)
    41. // if (res && res.code === 0) {
    42. // this.payDoneJump();
    43. // }
    44. })
    45. },
    46. //支付取消
    47. onCancel: function(orderid) {
    48. console.log('onCancel' + orderid)
    49. payCancel(orderid).then((data) => {
    50. console.log(data)
    51. })
    52. },
    53. //支付失败
    54. onError: function(error, payment) {console.log('error:',error);console.log('payment:',payment)}
    55. });
    56. },

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

    1. const loginFun = () => {
    2. Pi.init({ version: "2.0", sandbox: true });
    3. const scopes = ["payments", "username", "wallet_address"];
    4. function onIncompletePaymentFound(payment) {
    5. alert(1111111)
    6. console.log("payment", payment);
    7. return payIncomplete({
    8. identifier:payment.identifier,
    9. transaction:{
    10. _link:payment.transaction._link,
    11. txid:res.transaction.txid
    12. }
    13. })
    14. }
    15. Pi.authenticate(scopes, onIncompletePaymentFound).then(function (auth) {
    16. console.log("auth", auth);
    17. let userInfo = {
    18. accessToken: auth.accessToken,
    19. userId: auth.user.uid,
    20. userName: auth.user.username,
    21. };
    22. // userGetPush().then((data) => {
    23. // console.log(data);
    24. // userStore().userGetPush = data.data;
    25. // });
    26. Login(userInfo).then((data) => {
    27. console.log(data);
    28. if (data.status == "success") {
    29. // 将用户信息存入pinia
    30. userStore().userInfoChange(data.data);
    31. // 发布消息到socket Login() 存入userId
    32. // this.$socket.emit("login", data.data.userInfo.userId);
    33. router.push("/home");
    34. }
    35. });
    36. })
    37. .catch(function (error) {
    38. console.error(error);
    39. });
    40. };

    详细流程

    使用Pi-SDK功能发起支付

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

     

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

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

    1. @Override
    2. @Transactional(rollbackFor = Exception.class)
    3. public String approve(PaymentVO paymentVO) {
    4. log.error("approve-------------------------------------------------------------");
    5. OrderInfo orderInfo;
    6. log.error("paymentVO----------------------------------"+paymentVO);
    7. //获取付款信息
    8. OkHttpClient client = new OkHttpClient();
    9. Request request = new Request.Builder()
    10. .url("https://api.minepi.com/v2/payments/" + paymentVO.getPaymentId())
    11. .addHeader("Authorization", "Key " + commonConfig.getServerAccessKey())
    12. .build();
    13. try (Response response = client.newCall(request).execute()) {
    14. if (!response.isSuccessful()) {
    15. String string = response.body().string();
    16. JSONObject jsonObject1 = JSON.parseObject(string);
    17. log.error("!response-------------------------------------------------------------"+commonConfig.getServerAccessKey());
    18. throw new RuntimeException("payments error " + jsonObject1.getString("error_message"));
    19. }
    20. String string = response.body().string();
    21. log.error("response-------------------------------------------------------------"+string);
    22. JSONObject jsonObject1 = JSON.parseObject(string);
    23. //校验实际支付金额
    24. BigDecimal userFinalPrice = paymentVO.getAmount();
    25. if (userFinalPrice.compareTo(jsonObject1.getBigDecimal("amount")) < 0) {
    26. log.error(userFinalPrice+"response-------------------------------------------------------------"+jsonObject1.getBigDecimal("amount"));
    27. throw new RuntimeException("支付金额少于订单金额");
    28. }
    29. } catch (Exception e) {
    30. throw new BusinessException("支付失败,请联系后台人员"+e.getLocalizedMessage()+e.toString()+e.getCause().toString());
    31. }
    32. OkHttpClient client1 = new OkHttpClient();
    33. //信息真实,通知PI我准备好了,可以付款了
    34. Request request1 = new Request.Builder()
    35. .url("https://api.minepi.com/v2/payments/" + paymentVO.getPaymentId() + "/approve")
    36. .addHeader("Content-Type", "application/json")
    37. .addHeader("Access-Control-Allow-Origin", "*")
    38. .addHeader("Authorization", "Key " + commonConfig.getServerAccessKey())
    39. .post(RequestBody.create("", MediaType.parse("application/json")))
    40. .build();
    41. try (Response response1 = client1.newCall(request1).execute()) {
    42. if (!response1.isSuccessful()) {
    43. throw new RuntimeException("approve error: ");
    44. }
    45. log.error("response1-------------------------------------------------------------");
    46. //更新支付报文
    47. // tMerStoreGoodsOrderEntity.setPayOrderId(paymentDto.getPaymentId());
    48. // tMerStoreGoodsOrderEntity.setPayStatusType("10007002");//支付中
    49. // itMerStoreGoodsOrderService.updateById(tMerStoreGoodsOrderEntity);
    50. log.error("return-------------------------------------------------------------");
    51. } catch (RuntimeException | IOException e) {
    52. log.error("error-------------------------------------------------------------");
    53. e.printStackTrace();
    54. }
    55. // 生成订单
    56. orderInfo = new OrderInfo();
    57. orderInfo.setOrderId(StringUtil.generateShortId());
    58. orderInfo.setShopId(paymentVO.getShopId());
    59. orderInfo.setUserId(paymentVO.getUserId());
    60. orderInfo.setShopUserId(paymentVO.getShopUserId());
    61. orderInfo.setAmount(paymentVO.getAmount());
    62. orderInfoMapper.insert(orderInfo);
    63. log.error("生成订单-------------------------------------------------------------");
    64. return orderInfo.getOrderId();
    65. }

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

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

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

    1. /**
    2. * 前端支付完成,余额支付直接调用此方法
    3. */
    4. @Override
    5. @Transactional(rollbackFor = Exception.class)
    6. public ResponseVO complete(CompleteVO completeVO) {
    7. String payType = completeVO.getPayType();
    8. log.error("complete------------------------------------------------------------"+completeVO);
    9. if ("1".equals(payType)) {
    10. //余额支付
    11. String orderId = completeVO.getOrderId();
    12. String lockName = "access:lock:complete:" + orderId;
    13. String lockId = UUID.randomUUID();
    14. if (!redisLockUtil.tryLock(lockName, lockId, 20L)) {
    15. // 调用太快
    16. return new ResponseVO(PaymentEnum.getStatusByCode(4),4,PaymentEnum.getMsgByCode(4));
    17. }
    18. // 获取订单信息
    19. OrderInfo orderInfo = orderInfoMapper.selectOne(new QueryWrapper<OrderInfo>().eq("order_id", orderId));
    20. if ((orderInfo.getOrderStatus() != 0)) {
    21. // 订单不是待支付状态
    22. return new ResponseVO(PaymentEnum.getStatusByCode(2),2,PaymentEnum.getMsgByCode(2));
    23. }
    24. String userId = orderInfo.getUserId();
    25. AccountInfo accountInfo = accountInfoMapper.selectOne(new QueryWrapper<AccountInfo>()
    26. .eq("user_id", userId));
    27. BigDecimal balance = accountInfo.getPiBalance();
    28. if (balance.compareTo(orderInfo.getAmount()) < 0) {
    29. // 余额不足,请前往充值
    30. return new ResponseVO(PaymentEnum.getStatusByCode(5),5,PaymentEnum.getMsgByCode(5));
    31. }
    32. int update = orderInfoMapper.update(null,new UpdateWrapper<OrderInfo>()
    33. .eq("order_id",orderId)
    34. .set("order_status",1));
    35. balance=balance.subtract(orderInfo.getAmount());
    36. int update1 = accountInfoMapper.update(null, new UpdateWrapper<AccountInfo>()
    37. .eq("user_id", userId)
    38. .set("pi_balance", balance));
    39. // 支付成功
    40. return new ResponseVO(PaymentEnum.getStatusByCode(6),6,PaymentEnum.getMsgByCode(6));
    41. }
    42. //PI钱包支付
    43. String paymentId = completeVO.getPaymentId();//PI订单号
    44. String lockName = "access:lock:complete:" + paymentId;
    45. String lockId = UUID.randomUUID();
    46. log.error(paymentId+"-----------------"+lockName+"---------------------"+lockId);
    47. if (!redisLockUtil.tryLock(lockName, lockId, 20L)) {
    48. // 调用太快
    49. log.error("!RedisLockUtil---------------------------------------------------------调用太快");
    50. return new ResponseVO(PaymentEnum.getStatusByCode(4),4,PaymentEnum.getMsgByCode(4));
    51. }
    52. OrderInfo orderInfo = orderInfoMapper.selectOne(new QueryWrapper<OrderInfo>()
    53. .eq("order_id", completeVO.getOrderId()));
    54. log.error("orderId--------------------------------------------------------------"+orderInfo);
    55. if (null == orderInfo) {
    56. // 订单不存在
    57. log.error("!orderinfo--------------------------------------------------------不存在");
    58. return new ResponseVO(PaymentEnum.getStatusByCode(1),1,PaymentEnum.getMsgByCode(1));
    59. }
    60. log.error("orderinfo------------------------------------------------------------------"+orderInfo);
    61. if (orderInfo.getOrderStatus() != 0) {
    62. // 订单不是待支付状态
    63. log.error("!order---------------------------------------------------------pay");
    64. return new ResponseVO(PaymentEnum.getStatusByCode(2),2,PaymentEnum.getMsgByCode(2));
    65. }
    66. //通知PI完成交易
    67. JSONObject jsonObject = new JSONObject();
    68. jsonObject.put("txid", completeVO.getTxId());
    69. Map<String, String> heads = new HashMap<>();
    70. heads.put("Content-Type", "application/json;charset=UTF-8");
    71. heads.put("Authorization", "Key " + commonConfig.getServerAccessKey());
    72. log.error("pi-----------------------------------------"+jsonObject);
    73. try {
    74. HttpResponse response = HttpRequest.post("https://api.minepi.com/v2/payments/" + paymentId + "/complete")
    75. .headerMap(heads, false)
    76. .body(String.valueOf(jsonObject))
    77. .timeout(5 * 60 * 1000)
    78. .execute();
    79. String body = response.body();
    80. JSONObject jsonObject1 = JSON.parseObject(body);
    81. String error = jsonObject1.getString("error");
    82. if (!StringUtils.isEmpty(error)) {
    83. log.error("!strinutils-----------------------------"+body);
    84. throw new RuntimeException("订单完成异常!");
    85. }
    86. orderInfo.setOrderStatus(1);
    87. // 更新订单
    88. orderInfoMapper.updateById(orderInfo);
    89. log.error("支付成功------------------------------------------------------");
    90. // 支付成功
    91. return new ResponseVO(PaymentEnum.getStatusByCode(6),6,PaymentEnum.getMsgByCode(6));
    92. } catch (Exception e) {
    93. throw e;
    94. }
    95. }

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

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

    路由到后端的接口 

     

     后端接口代码

    1. @Override
    2. public ResponseVO incomplete(IncompleteVO incompleteVO) {
    3. log.error("incomplete--------------------------------");
    4. try {
    5. //先处理未完成的订单
    6. String oldpaymentId = incompleteVO.getIdentifier();
    7. TransactionVO transaction = incompleteVO.getTransaction();
    8. log.error("?transation--------------------"+transaction);
    9. log.error("?oldpaymentId------------------"+oldpaymentId);
    10. if (null != transaction) {
    11. log.error("transation--------------------"+transaction);
    12. log.error("oldpaymentId------------------"+oldpaymentId);
    13. String txid = transaction.getTxid();
    14. String txURL = transaction.get_link();
    15. // OrderInfo orderInfo = orderInfoMapper.selectOne(new QueryWrapper<OrderInfo>().eq("order_id", oldpaymentId));
    16. // if (null == orderInfo) {
    17. // log.error("order-----------------null");
    18. // throw new RuntimeException("旧订单不存在");
    19. // }
    20. //
    21. // if (orderInfo.getOrderStatus()==1) {
    22. // log.error("orderStatus---------------------"+orderInfo.getOrderStatus());
    23. // throw new RuntimeException("订单是已支付状态");
    24. // }
    25. String get = HttpClientUtil.sendGet(txURL);
    26. JSONObject jsonObject1 = JSON.parseObject(get);
    27. String piOrderId = jsonObject1.getString("memo");//我方订单ID
    28. log.error("memo---------------------"+piOrderId);
    29. JSONObject jsonObject = new JSONObject();
    30. jsonObject.put("txid", txid);
    31. Map<String, String> heads = new HashMap<>();
    32. heads.put("Content-Type", "application/json;charset=UTF-8");
    33. heads.put("Authorization", "Key " + commonConfig.getServerAccessKey());
    34. try {
    35. HttpResponse response = HttpRequest.post("https://api.minepi.com/v2/payments/" + piOrderId + "/complete")
    36. .headerMap(heads, false)
    37. .body(String.valueOf(jsonObject))
    38. .timeout(5 * 60 * 1000)
    39. .execute();
    40. String body = response.body();
    41. JSONObject jsonObject2 = JSON.parseObject(body);
    42. String error = jsonObject2.getString("error");
    43. if (!StringUtils.isEmpty(error)) {
    44. log.error("!response------------------"+error);
    45. throw new RuntimeException("订单完成异常!");
    46. }
    47. return new ResponseVO(PaymentEnum.getStatusByCode(7),7,PaymentEnum.getMsgByCode(7));
    48. } catch (Exception e) {
    49. throw e;
    50. }
    51. }
    52. } catch (Exception e) {
    53. return new ResponseVO(PaymentEnum.getStatusByCode(8),8,PaymentEnum.getMsgByCode(8));
    54. }
    55. return new ResponseVO(PaymentEnum.getStatusByCode(8),8,PaymentEnum.getMsgByCode(8));
    56. }

  • 相关阅读:
    【STM32】标准库-读写内部Flash及用户选项字节
    一个UI设计师自学编程的经历
    关于 Vue3 响应式 API 以及 reactive 和 ref 的用法
    2000-2021年各省GDP包括名义GDP、实际GDP、GDP平减指数(以2000年为基期)
    vue3.0中使用echarts,鼠标悬浮无法显示数据框的问题
    【leetcode热题Hot100】——LRU缓存
    字符串的认识
    Zookeeper系列——1Zookeeper简介及部署
    【支付宝生态质量验收与检测技术】
    Seaborn绘制热力图的子图
  • 原文地址:https://blog.csdn.net/qq_63431773/article/details/132129639