Custom Payment Provider
Pro edition
This section introduces a Pro edition feature.
In addition to the payment providers already supported by Cloudreve, you can also integrate your own payment platform by implementing Cloudreve's payment interface, or bridge other third-party platforms.
To implement a custom payment interface, you need an independent HTTP service that provides:
- An API endpoint to:
- Handle Cloudreve's payment creation requests and return a payment page URL;
- Handle Cloudreve's requests to query the payment status of an order;
- After the customer completes the payment, send an HTTP request to a specified URL to notify Cloudreve of the payment completion.
Implementing the Payment Interface
Implement your payment interface according to the specifications in this chapter, deploy the interface, and ensure it can communicate with Cloudreve over the network.
Warning
The custom payment API for version 3 is not compatible with version 4.
Create Order POST <your-payment-endpoint>
When a new order is created, Cloudreve sends a request to your payment interface.
Request Headers
Name | Type | Description |
---|---|---|
Authorization | String | A signature calculated using the Communication key you set in the backend. See Verify Signature for details. |
X-Cr-Version | String | Cloudreve version number |
X-Cr-Site-Id | String | Cloudreve site ID, which can be used to distinguish different sites |
X-Cr-Site-Url | String | The main site URL of Cloudreve. |
Request Body
The request body is sent in JSON format and includes the following fields:
Name | Type | Description |
---|---|---|
name | String | Payment name |
order_no | String | Order number |
notify_url | String | Callback notification URL |
amount | Number | Payment amount, using the smallest unit of currency |
currency | String | ISO 4217 code of the currency |
Request Example
POST /order
Host: examplepayment.com
Authorization: Bearer Vep6hl1x8fiQLasEauMEUqxFKyEqSXb9D_BBQpOiTd8=:1676027218
X-Cr-Site-Url: https://demo.cloudreve.org
X-Cr-Site-Id: b7de8bba-8f86-40fe-8171-c2625b6c4a61
X-Cr-Version: 4.0.0
{
"name": "Unlimited Storage",
"order_no": "20230209190648343421",
"notify_url": "http://demo.cloudreve.org/api/v4/callback/custom/20230209190648343421",
"amount": 8900,
"currency": "CNY"
}
Expected Response
Regardless of whether the order creation is successful, the HTTP response code should be 200
, with success or failure determined by the code
field in the response body.
HTTP/1.1 200 OK
{
// A successful response is always 0
"code": 0,
// The URL of the payment checkout page, which will be generated as a QR code for the user to scan, or they can choose to open this URL directly
"data": "https://examplepayment.com/checkout/26544743"
}
Query Order Status GET <your-payment-endpoint>
When Cloudreve needs to query the order status, it sends a request to your endpoint.
Request Headers
Name | Type | Description |
---|---|---|
Authorization | String | A signature calculated using the Communication key you set in the backend. See Verify Signature for details. |
X-Cr-Version | String | Cloudreve version number |
X-Cr-Site-Id | String | Cloudreve site ID, which can be used to distinguish different sites |
X-Cr-Site-Url | String | The main site URL of Cloudreve. |
Request Parameters
Request parameters are sent as URL parameters and include the following fields:
Name | Type | Description |
---|---|---|
order_no | String | Order number |
Request Example
GET /order?order_no=20230209190648343421
Host: examplepayment.com
Authorization: Bearer Vep6hl1x8fiQLasEauMEUqxFKyEqSXb9D_BBQpOiTd8=:1676027218
X-Cr-Site-Url: https://demo.cloudreve.org
X-Cr-Site-Id: b7de8bba-8f86-40fe-8171-c2625b6c4a61
X-Cr-Version: 4.0.0
Expected Response
HTTP/1.1 200 OK
{
"code": 0,
// PAID - Paid
// Other values - Not paid
"data": "PAID"
}
Verify Signature
You can set a communication key
in the Cloudreve payment settings. Cloudreve's payment creation requests will use this key for signing and place it in the Request:
- For payment creation requests, the signature is placed in the
Authorization
header, prefixed withBearer Cr
, and the prefix should be removed (note that the prefix should not have any spaces); - For query order status requests, the signature is placed in the URL parameter
sign
.
Verify the signature using the following algorithm:
- Extract the signature from the request, split the string by
:
, and the second part is the expiration timestamp of the signature, noted astimestamp
. Verify that it is greater than the current timestamp. The part before:
is noted assignature
;
// For payment creation requests
authHeader := r.Header.Get("Authorization")
if !strings.HasPrefix(authHeader, "Bearer Cr ") {
return fmt.Errorf("invalid Authorization header format")
}
signaturePart := strings.TrimPrefix(authHeader, "Bearer Cr ")
parts := strings.Split(signaturePart, ":")
if len(parts) != 2 {
return fmt.Errorf("invalid signature format in header")
}
signature, timestampStr := parts[0], parts[1]
// For query order status requests
signaturePart := r.URL.Query().Get("sign")
parts := strings.Split(signaturePart, ":")
if len(parts) != 2 {
return fmt.Errorf("invalid signature format in URL")
}
signature, timestampStr := parts[0], parts[1]
- Verify that
timestamp
is greater than the current timestamp. If it is less, return an error.
timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
if err != nil {
return fmt.Errorf("invalid timestamp: %w", err)
}
if time.Now().Unix() > timestamp {
return fmt.Errorf("signature expired")
}
Get the string to be signed:
For payment creation requests:
- Iterate through all request headers, filter out the headers that start with
X-Cr-
, convert them tokey=value
format, then sort them and join them with&
to form the stringsignedHeaderStr
.
govar signedHeader []string for k, _ := range r.Header { if strings.HasPrefix(k, "X-Cr-") { signedHeader = append(signedHeader, fmt.Sprintf("%s=%s", k, r.Header.Get(k))) } } sort.Strings(signedHeader) signedHeaderStr := strings.Join(signedHeader, "&")
- Encode the
Path
part of the request URL, request body, andsignedHeaderStr
as a JSON stringsignContent
. IfPath
is empty, use/
instead.
gotype RequestRawSign struct { Path string Header string Body string } path := r.URL.Path if path == "" { path = "/" } signContent, err := json.Marshal(RequestRawSign{ Path: path, Header: signedHeaderStr, Body: string(r.Body), })
- Iterate through all request headers, filter out the headers that start with
For query order status requests: Just use the
Path
part (excludingQuery
) of the request URL assignContent
. IfPath
is empty, use/
instead.gopath := r.URL.Path if path == "" { path = "/" }
Concatenate
signContent
andtimestamp
with:
to form the stringsignContentFinal
, and use the HMAC algorithm andCommunication key
to calculate the signature forsignContentFinal
, noted assignActual
. Use URL-safe Base64 encoding for the signature.
signContentFinal := fmt.Sprintf("%s:%s", signContent, timestampStr)
h := hmac.New(sha256.New, []byte(communicationKey))
h.Write([]byte(signContentFinal))
signActual := base64.URLEncoding.EncodeToString(h.Sum(nil))
- Compare
signActual
withsignature
to check for consistency.
if signActual != signature {
return fmt.Errorf("invalid signature")
}
Send Callback
After the user completes the payment, you need to send a GET request to the notify_url
specified when the payment was created to notify Cloudreve that the user has completed the payment. If the callback request fails, retry with exponential backoff unless the response explicitly returns an error message and code. An example response from Cloudreve is as follows:
HTTP/1.1 200 OK
{
// A successful response is always 0
"code": 0
}