处理交易状态
# 处理交易状态
1. 若交易同步返回状态为“ PROCESSING”,商户应等待 pingpong checkout 异步结果再进行业务处理。
2. 因此商户必须有机制保证在合适的时机查询到交易最终状态,或者接入异步通知
在唤起收银台,点击支付无错误的情况下,就产生了待处理的交易。PingPongPay提供两种获取交易状态的方式:
# 主动查询
调用接口 v2/query 可以主动查询单笔交易
# 请求地址
https://{host}/v2/query
# 请求参数
参数字段 | 参数类型 | 参数属性 | 参数说明 |
---|---|---|---|
accId | String(64) | M | PingPong 商户店铺编号 |
signType | String(8) | M | 签名规约,支持 MD5、SHA256,具体⻅本文 “签名规约”一栏 |
sign | String(256) | M | 签名内容,具体⻅本文“签名规约”一栏 |
transactionId | String(64) | C | PingPong 交易流水号,与 merchantTransactionId 至少上送一项 |
merchantTransactionId | String(64) | C | 商户网站交易流水号,与 transactionId 至少 上送一项 |
# 响应参数
参数字段 | 参数属性 | 参数说明 |
---|---|---|
clientId | M | PingPong 商户商户号 |
accId | M | PingPong 商户店铺编号 |
transactionId | M | PingPong 交易流水号 |
merchantTransactionId | M | 商户网站的的交易流水号 |
code | M | 结果状态码 |
description | M | 结果描述 |
paymentType | M | 交易类型: DEBIT-直接付款 AUTH-预授权 REFUND-退款 CAPTURE-预授权确认 VOID-预授权取消 APPROVE-审核通过 REJECT-审核拒绝 |
currency | M | 交易币种 |
amount | M | 交易金额 |
transactionTime | M | 交易发起时间,yyyyMMddHHmmss |
completeTime | M | 交易处理完成时间,yyyyMMddHHmmss |
status | M | SUCCESS-成功 FAILED-失败 PROCESSING-进行中 REVIEW-待审核 |
signType | M | 签名规约,支持 MD5、SHA256,具体⻅本文“签名 方案”一栏 |
sign | M | 签名内容,具体⻅本文“签名规约”一栏 |
remark | C | 商户扩展字段 |
threeDSecure | C | 3DS 交易标示 Y/N |
# 交易结果异步通知
商户正常上报交易参数无误,发起正常的交易请求与流程,并且上送了notifyUrl,则会收到异步通知
pingpong checkout API v2 通过 POST 商户上送的notifyUrl 来发起通知,报文装填形式为String,并携带以下MIMEType:
Content-Type:x-www-form-urlencoded
# 报文示例
"notificationUrl=http://www.example.com/&amount=1.080000&clientId=2018092714313010016&code=000000&threeDSecure=N&sign=23EA484AFA4F39A5E1A53E7E379730AB&description=Transaction succeeded&transactionTime=1639638212000&transactionId=PS21121615033172714&checkoutType=NORMAL&paymentType=DEBIT&outFlowId=PS21121615033172714&merchantTransactionId=PShop20211216150322GA&accId=2018092714313010016291&signType=MD5¤cy=USD&status=SUCCESS"
# 报文接收示例
$inputStr = file_get_contents('php://input');
parse_str($inputStr, $input);
2
@RequestMapping(value = "/checkoutNotify", method = {RequestMethod.GET, RequestMethod.POST})
public String checkoutCallback(HttpServletRequest httpServletRequest,HttpServletResponse response) {
String bodyJson = NotifyUtils.getBodyJson(httpServletRequest);
log.info("receive remote checkoutCallback notifyContent: {}", bodyJson);
boolean result = callbackService.checkoutCallback(bodyJson);
response.setStatus(result ? HttpServletResponse.SC_OK : HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return result ? "OK" : "ERROR";
}
public class NotifyUtils {
public static String getBodyString(HttpServletRequest httpServletRequest) {
StringBuilder sb = new StringBuilder();
ServletInputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = httpServletRequest.getInputStream();
reader = new BufferedReader(
new InputStreamReader(inputStream, StandardCharsets.UTF_8));
char[] bodyCharBuffer = new char[1024];
int len = 0;
while ((len = reader.read(bodyCharBuffer)) != -1) {
sb.append(new String(bodyCharBuffer, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
public static String getBodyJson(HttpServletRequest httpServletRequest) {
String bodyString = NotifyUtils.getBodyString(httpServletRequest);
String[] StringList = StringUtils.split(bodyString, "&");
List<HashMap<String, String>> list = new ArrayList<>();
HashMap<String, String> map = new HashMap<>(1);
for (String stringItem :
StringList) {
String[] chunks = StringUtils.split(stringItem, "=");
map.put(chunks[0], chunks[1]);
}
return JSON.toJSONString(map);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// Make sure to add code blocks to your code group
商户在成功收到异步处理结果后,需要在响应体中返回 HttpCode 为 200 并 且以字符串形式返回 OK, 表示商户已经成功收到异步结果通知,否则 pingpong checkout 会在随后的一 段时间内,以递增的时间间隔重发,最多重发 12 次,间隔为 2s/5s/5s/10s/30s/1m/10m/30m/1h/2h/1d/2d。
具体送达时间受双方网络连接状态影响。。
商户不能仅仅依赖于异步通知,如果⻓时间未收到交易结果,商户应该主动向 pingpong checkout 发起交易查询,查询对应的交易结果。
1. notifyUrl需要填写商户自己系统的真实地址,不能随意填写
2. notifyUrl必须是以https://的完整全路径地址,并且确保url中的域名和IP是外网可以访问的,不能填写localhost、127.0.0.1、192.168.x.x等本地或内网IP
3. notifyUrl填写之前,商户需要尝试请求并自行检查网络连通性
4. notify_url最后不携带参数,以免丢失参数,若一定要携带,请使用pathInfo的URL模式
5. 异步通知可能不是最终结果,可能是PROCESSING,REVIEW等状态,需要注意处理
6. 异步通知代码处理逻辑不能做登录态校验。
7. 同样的通知可能会多次发送给商户系统,商户系统必须能够正确处理重复的通知。
8. 商户侧对IP有防火墙策略限制的应将IP加入白名单
生产 Notify Sever IP | 沙箱 Notify Sever IP |
---|---|
3.125.243.2 | 52.76.198.228 |
3.126.196.22 | |
18.195.199.34 |
# 如何判断交易状态
# 交易状态
交易状态是交易流程和结果的描述,不同业务模型有不同的规则,PingPong Pay API v2 采用paymentType来区分业务, status表示接口调用状态。paymentType+status是完整的交易状态描述。
对于PingPongPay API v2 现有四种status可能被返回:
1. status只是对于当前请求操作的成功与否的描述,不能简单的等于交易状态
2. 对于DEBIT业务,status与实际交易状态一致
3. 对于接入了AUTH的用户,交易状态应根据paymentType+status进行组合判断
# paymentType+status
# DEBIT交易业务:
状态 | 描述 | 状态类型 |
---|---|---|
SUCCESS | 已成功支付,交易成功 | 终态 |
FAILED | 交易失败 | 本次支付终态,可以请求再次支付 |
PROCESSING | 中间状态,等待进一步处理 | 中间状态 |
REVIEW | 交易具有风险,需要人工审核 | 中间状态 |
一旦交易到达终态,除非因为失败发起重新支付,都不应该再次接受异步通知的更改。交易在中间态的时候,需要等待下一个异步通知到达本次支付的终态。
# AUTH交易业务:
状态 | 描述 |
---|---|
SUCCESS | 不等于交易成功,需要根据具体的操作类型来判断 |
FAILED | 交易失败 |
PROCESSING | 中间状态,等待进一步处理 |
REVIEW | 交易具有风险,需要人工审核 |
# AUTH业务在checkout接口中的状态
操作 | 状态 | 描述 |
---|---|---|
AUTH | SUCCESS | 预授权操作成功,不代表交易最终状态成功,后续还需发起CAPTURE |
AUTH | FAILED | 预授权操作失败 |
详见预授权章节。