概述
用户完成付款后,Oceanpayment会向商户请求中提交的参数noticeUrl发送异步支付通知。通知包含订单号、支付状态、支付时间及其他交易信息,供商户验证并更新订单状态。
在此之前
信息
请参阅Oceanpayment通知重推机制与规范,了解通知失败后的重推规则、重推时间及商户响应要求。
支付状态更新说明
同一笔订单可能先收到支付失败通知,之后又收到支付成功通知。商户收到成功通知后,应以最新支付状态为准。请根据通知中的订单号、支付id、支付状态和支付时间更新订单状态。
- Oceanpayment向商户的
noticeUrl推送该订单的支付结果通知。 - 商户验签通过后,将订单更新为最新状态,并执行后续业务处理,例如确认订单或安排发货。
通知参数
| 参数名 | 类型 | 描述 | 示例 |
|---|---|---|---|
notice_type | string | 推送类型
| transaction |
push_dateTime | string | 推送时间
| 2026-10-01 10:00:00 |
response_type | string | 回调类型
| 1 |
account | string | Oceanpayment账户号 | 995149 |
terminal | string | Oceanpayment终端号 | 99514901 |
signValue | string | 安全签名,用于验证交易的安全性,使用SHA256加密 | e14328dc67b203de5048d999f3ccd054d1bad550a0821381ce508176329cb7ee |
methods | string | 支付方式 | Credit Card |
order_number | string | 网站订单号 | NO123456789 |
card_country | string | 卡属国家 | US |
order_currency | string | 订单号的交易币种
| USD |
order_amount | string | 订单号的交易金额
| 0.1 |
order_notes | string | 订单备注信息,返回时则原样返回 | Remark |
card_number | string | 持卡人的信用卡卡号
| 411111***1111 |
card_type | string | 卡种 | Visa |
payment_country | string | 消费者IP所在国家 | US |
payment_id | string | 支付ID,Oceanpayment的支付唯一单号 | 20261030094706490119917358 |
quickpay_id | string | Quickpay ID(唯一),采用UUID形式 只有QuickPay支付才有返回 | 7c910aaf-4760-4467-bd23-d8157e01844b |
payment_status | string | 该笔交易的结果状态
| 1 |
payment_authType | string | 该笔交易的类型
| 0 |
payment_details | string | 该笔交易的支付详情
| 80000:Transaction Approved |
payment_solutions | string | 付款失败后的解决方法
| Contact the merchant website to confirm the payment result |
payment_risk | string | 未通过的风控规则
| PriorChannel=0.0; |
payment_amount | string | 消费者实际付款金额 | 0.1 |
payment_exchangeRate | string | 汇率 | 0.85358155 |
pay_barCode | string | 订单打印码 | 4500011583202610306638447730 |
auth_reason | string | 预授权操作原因 | Confirm authorization |
order_operator | string | 账户后台预授权交易处理操作人 | Operator |
auth_code | string | 返回信用卡的授权码
| 1793325793759R9 |
pay_userId | string | 消费者ID | USER_9D7A2F6C1B84 |
card_cvvResultCode | string | 返回CVV银行代码及说明 | 01 |
支付状态
在异步通知参数中,payment_status表示支付结果状态。各状态的详细说明请参阅付款状态文档。
响应代码
在异步通知参数中,payment_details表示交易的支付详情,格式为[代码Code:支付详情]。有关代码说明,请参阅支付响应码。
返回XML示例
<?xml version="1.0" encoding="UTF-8"?>
<response>
<notice_type>transaction</notice_type>
<push_dateTime></push_dateTime>
<response_type>1</response_type>
<account>995149</account>
<terminal>99514901</terminal>
<signValue>0B1FAEDCD3DE9FB72F13A0DB80955479B00F2E6F7A4BF5C84A3403C1EAE489BE</signValue>
<methods>Credit Card</methods>
<order_number>NO12345678</order_number>
<card_type>Visa</card_type>
<card_country>US</card_country>
<order_currency>USD</order_currency>
<order_amount>1.99</order_amount>
<order_notes></order_notes>
<card_number>411111***1111</card_number>
<payment_country>CN</payment_country>
<payment_id>180808092746539010540</payment_id>
<payment_authType>0</payment_authType>
<payment_status>1</payment_status>
<payment_details>Successful test transaction</payment_details>
<payment_solutions></payment_solutions>
<payment_risk></payment_risk>
<payment_amount>1.00</payment_amount>
<payment_exchangeRate></payment_exchangeRate>
<pay_userId></pay_userId>
<pay_barCode>5b87b4528d3a66e4a59e51577960d552461c997a32c71e3c</pay_barCode>
<auth_reason></auth_reason>
<order_operator></order_operator>
</response>
解析示例
通过交易请求的参数noticeUrl进行接收:
- PHP
- Java
- Python
- Go
- Node.js
//获取推送输入流XML
$xml_str = file_get_contents("php://input");
//判断返回的输入流是否为xml
if(xml_parser($xml_str)){
$xml = simplexml_load_string($xml_str);
//把推送参数赋值到$_REQUEST
$_REQUEST['response_type'] = (string)$xml->response_type;
$_REQUEST['account'] = (string)$xml->account;
$_REQUEST['terminal'] = (string)$xml->terminal;
$_REQUEST['payment_id'] = (string)$xml->payment_id;
$_REQUEST['order_number'] = (string)$xml->order_number;
$_REQUEST['order_currency'] = (string)$xml->order_currency;
$_REQUEST['order_amount']= (string)$xml->order_amount;
$_REQUEST['payment_status']= (string)$xml->payment_status;
$_REQUEST['payment_details'] = (string)$xml->payment_details;
$_REQUEST['signValue'] = (string)$xml->signValue;
$_REQUEST['order_notes'] = (string)$xml->order_notes;
$_REQUEST['card_number'] = (string)$xml->card_number;
$_REQUEST['payment_authType'] = (string)$xml->payment_authType;
$_REQUEST['payment_risk'] = (string)$xml->payment_risk;
$_REQUEST['methods'] = (string)$xml->methods;
$_REQUEST['payment_country'] = (string)$xml->payment_country;
$_REQUEST['payment_solutions'] = (string)$xml->payment_solutions;
}
//判断是否为xml
function xml_parser($str){
$xml_parser = xml_parser_create();
if(!xml_parse($xml_parser,$str,true)){
xml_parser_free($xml_parser);
return false;
}else {
return true;
}
}
import org.w3c.dom.*;
import javax.xml.parsers.*;
import org.xml.sax.InputSource;
import java.io.StringReader;
import java.util.*;
String[] fields = {
"response_type", "account", "terminal", "payment_id",
"order_number", "order_currency", "order_amount",
"payment_status", "payment_details", "signValue",
"order_notes", "card_number", "payment_authType",
"payment_risk", "methods", "payment_country",
"payment_solutions"
};
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
Document document = factory.newDocumentBuilder()
.parse(new InputSource(new StringReader(xmlString)));
Map<String, String> data = new HashMap<>();
for (String field : fields) {
NodeList nodes = document.getElementsByTagName(field);
data.put(field, nodes.getLength() > 0
? nodes.item(0).getTextContent()
: "");
}
// 示例:读取字段
String paymentId = data.get("payment_id");
String paymentStatus = data.get("payment_status");
import xml.etree.ElementTree as ET
fields = [
"response_type", "account", "terminal", "payment_id",
"order_number", "order_currency", "order_amount",
"payment_status", "payment_details", "signValue",
"order_notes", "card_number", "payment_authType",
"payment_risk", "methods", "payment_country",
"payment_solutions",
]
root = ET.fromstring(xml_string)
data = {
field: root.findtext(field, default="")
for field in fields
}
# 示例:读取字段
payment_id = data["payment_id"]
payment_status = data["payment_status"]
package main
import "encoding/xml"
type PaymentNotification struct {
ResponseType string `xml:"response_type"`
Account string `xml:"account"`
Terminal string `xml:"terminal"`
PaymentID string `xml:"payment_id"`
OrderNumber string `xml:"order_number"`
OrderCurrency string `xml:"order_currency"`
OrderAmount string `xml:"order_amount"`
PaymentStatus string `xml:"payment_status"`
PaymentDetails string `xml:"payment_details"`
SignValue string `xml:"signValue"`
OrderNotes string `xml:"order_notes"`
CardNumber string `xml:"card_number"`
PaymentAuthType string `xml:"payment_authType"`
PaymentRisk string `xml:"payment_risk"`
Methods string `xml:"methods"`
PaymentCountry string `xml:"payment_country"`
PaymentSolutions string `xml:"payment_solutions"`
}
var data PaymentNotification
err := xml.Unmarshal([]byte(xmlString), &data)
if err != nil {
// XML 解析失败
}
// 示例:读取字段
paymentID := data.PaymentID
paymentStatus := data.PaymentStatus
npm install fast-xml-parser
const { XMLParser } = require("fast-xml-parser");
const parser = new XMLParser({
ignoreAttributes: true,
trimValues: false,
});
const parsed = parser.parse(xmlString);
// 假设 XML 根节点为 <response>。
// 如果实际根节点名称不同,请相应修改 "response"。
const data = parsed.response;
const fields = [
"response_type", "account", "terminal", "payment_id",
"order_number", "order_currency", "order_amount",
"payment_status", "payment_details", "signValue",
"order_notes", "card_number", "payment_authType",
"payment_risk", "methods", "payment_country",
"payment_solutions",
];
for (const field of fields) {
data[field] ??= "";
}
// 示例:读取字段
const paymentId = data.payment_id;
const paymentStatus = data.payment_status;
安全验证签名
| 类型 | 签名结构 |
|---|---|
| 交易 | account+terminal+order_number+order_currency+order_amount+order_notes+card_number+payment_id+ payment_authType+payment_status+payment_details+payment_risk+secureCode |
签名验证示例
- PHP
- Java
- Python
- Go
- Node.js
$_REQUEST['signValue'] = (string)$xml->signValue;
//获取本地的secureCode值
$secureCode = {secureCode};
$local_signValue = hash("sha256",$_REQUEST['account'].$_REQUEST['terminal'].$_REQUEST['order_number'].$_REQUEST['order_currency'].$_REQUEST['order_amount'].$_REQUEST['order_notes'].$_REQUEST['card_number'].$_REQUEST['payment_id'].$_REQUEST['payment_authType'].$_REQUEST['payment_status'].$_REQUEST['payment_details'].$_REQUEST['payment_risk'].$secureCode);
//加密串校验
if (strtolower($local_signValue) == strtolower($_REQUEST['signValue'])) {
if ($_REQUEST['payment_status'] == 1) {
//支付成功
}elseif($_REQUEST['payment_status'] == 0){
//支付失败
}elseif($_REQUEST['payment_status'] == -1){
//待处理
if ($_REQUEST['payment_authType'] == 1){
//处理预授权
}else{
//支付失败
}
}
}else{
//校验失败
}
echo "receive-ok";
exit;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Map;
public class PaymentNotification {
static String value(Map<String, String> data, String key) {
return data.getOrDefault(key, "");
}
static String sha256(String text) throws Exception {
byte[] hash = MessageDigest.getInstance("SHA-256")
.digest(text.getBytes(StandardCharsets.UTF_8));
StringBuilder hex = new StringBuilder();
for (byte b : hash) {
hex.append(String.format("%02x", b));
}
return hex.toString();
}
static void handle(Map<String, String> data, String secureCode) throws Exception {
String signingText =
value(data, "account") +
value(data, "terminal") +
value(data, "order_number") +
value(data, "order_currency") +
value(data, "order_amount") +
value(data, "order_notes") +
value(data, "card_number") +
value(data, "payment_id") +
value(data, "payment_authType") +
value(data, "payment_status") +
value(data, "payment_details") +
value(data, "payment_risk") +
secureCode;
String localSignValue = sha256(signingText);
String receivedSignValue = value(data, "signValue");
if (localSignValue.equalsIgnoreCase(receivedSignValue)) {
String status = value(data, "payment_status");
String authType = value(data, "payment_authType");
if ("1".equals(status)) {
// 支付成功
} else if ("0".equals(status)) {
// 支付失败
} else if ("-1".equals(status)) {
if ("1".equals(authType)) {
// 处理预授权
} else {
// 支付失败
}
}
} else {
// 验签失败
}
return "receive-ok"
}
}
import hashlib
import hmac
def handle_payment_notification(data: dict, secure_code: str):
fields = [
"account",
"terminal",
"order_number",
"order_currency",
"order_amount",
"order_notes",
"card_number",
"payment_id",
"payment_authType",
"payment_status",
"payment_details",
"payment_risk",
]
signing_text = "".join(data.get(field, "") for field in fields) + secure_code
local_sign_value = hashlib.sha256(signing_text.encode("utf-8")).hexdigest()
received_sign_value = data.get("signValue", "")
if hmac.compare_digest(local_sign_value.lower(), received_sign_value.lower()):
status = data.get("payment_status", "")
auth_type = data.get("payment_authType", "")
if status == "1":
# 支付成功
pass
elif status == "0":
# 支付失败
pass
elif status == "-1":
if auth_type == "1":
# 处理预授权
pass
else:
# 支付失败
pass
else:
# 验签失败
pass
# HTTP 响应正文返回纯文本 receive-ok
return "receive-ok"
package main
import (
"crypto/sha256"
"encoding/hex"
"encoding/xml"
"io"
"log"
"net/http"
"os"
"strings"
)
type PaymentNotification struct {
Account string `xml:"account"`
Terminal string `xml:"terminal"`
OrderNumber string `xml:"order_number"`
OrderCurrency string `xml:"order_currency"`
OrderAmount string `xml:"order_amount"`
OrderNotes string `xml:"order_notes"`
CardNumber string `xml:"card_number"`
PaymentID string `xml:"payment_id"`
PaymentAuthType string `xml:"payment_authType"`
PaymentStatus string `xml:"payment_status"`
PaymentDetails string `xml:"payment_details"`
PaymentRisk string `xml:"payment_risk"`
SignValue string `xml:"signValue"`
}
func notifyHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
body, err := io.ReadAll(r.Body)
if err != nil {
log.Printf("读取通知内容失败: %v", err)
writeAck(w)
return
}
var data PaymentNotification
if err := xml.Unmarshal(body, &data); err != nil {
log.Printf("解析通知 XML 失败: %v", err)
writeAck(w)
return
}
secureCode := os.Getenv("SECURE_CODE")
signingText := data.Account +
data.Terminal +
data.OrderNumber +
data.OrderCurrency +
data.OrderAmount +
data.OrderNotes +
data.CardNumber +
data.PaymentID +
data.PaymentAuthType +
data.PaymentStatus +
data.PaymentDetails +
data.PaymentRisk +
secureCode
hash := sha256.Sum256([]byte(signingText))
localSignValue := hex.EncodeToString(hash[:])
if strings.EqualFold(localSignValue, data.SignValue) {
switch data.PaymentStatus {
case "1":
// 支付成功
case "0":
// 支付失败
case "-1":
if data.PaymentAuthType == "1" {
// 处理预授权
} else {
// 支付失败
}
}
} else {
// 验签失败
}
writeAck(w)
}
func writeAck(w http.ResponseWriter) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("receive-ok"))
}
func main() {
http.HandleFunc("/payment/notify", notifyHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
const crypto = require("crypto");
function handlePaymentNotification(data, secureCode) {
const fields = [
"account",
"terminal",
"order_number",
"order_currency",
"order_amount",
"order_notes",
"card_number",
"payment_id",
"payment_authType",
"payment_status",
"payment_details",
"payment_risk",
];
const signingText =
fields.map((field) => data[field] ?? "").join("") + secureCode;
const localSignValue = crypto
.createHash("sha256")
.update(signingText, "utf8")
.digest("hex");
const receivedSignValue = data.signValue ?? "";
const signValid =
localSignValue.toLowerCase() === receivedSignValue.toLowerCase();
if (signValid) {
const { payment_status: status, payment_authType: authType } = data;
if (status === "1") {
// 支付成功
} else if (status === "0") {
// 支付失败
} else if (status === "-1") {
if (authType === "1") {
// 处理预授权
} else {
// 支付失败
}
}
} else {
// 验签失败
}
// HTTP 响应正文返回纯文本 receive-ok
return "receive-ok";
}