pi支付流程图:
- 使用Pi SDK功能发起支付
- 由 Pi SDK 自动调用的回调函数(让您的应用服务器知道它需要发出批准 API 请求)
- 从您的应用程序服务器到 Pi 服务器的 API 请求以批准付款(让 Pi 服务器知道您知道此付款)
- Pi浏览器向用户显示付款详细信息页面,我们正在等待用户签署交易
- 由 Pi SDK 自动调用的回调函数(让您的应用服务器知道它需要发出完整的 API 请求)
- 从您的应用服务器到 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));
}