Skip to content

Commit c8870c8

Browse files
committed
doc: custom payment provider
1 parent 25aafc6 commit c8870c8

File tree

4 files changed

+485
-0
lines changed

4 files changed

+485
-0
lines changed

.vitepress/en.mts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ export default defineConfig({
7373
},
7474
],
7575
},
76+
{
77+
text: "Custom Payment Provider",
78+
link: "/en/payment/custom",
79+
},
7680
{ text: "Slave Node", link: "/en/usage/slave-node" },
7781
],
7882
},

.vitepress/zh.mts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ export default defineConfig({
7575
},
7676
],
7777
},
78+
{
79+
text: "自定义支付渠道",
80+
link: "/zh/payment/custom",
81+
},
7882
{ text: "从机节点", link: "/zh/usage/slave-node" },
7983
],
8084
},

en/payment/custom.md

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
# Custom Payment Provider {#custom-payment-channel}
2+
3+
::: tip <Badge type="tip" text="Pro edition" />
4+
This section introduces a Pro edition feature.
5+
:::
6+
7+
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.
8+
9+
To implement a custom payment interface, you need an independent HTTP service that provides:
10+
11+
- An API endpoint to:
12+
- Handle Cloudreve's payment creation requests and return a payment page URL;
13+
- Handle Cloudreve's requests to query the payment status of an order;
14+
- After the customer completes the payment, send an HTTP request to a specified URL to notify Cloudreve of the payment completion.
15+
16+
## Implementing the Payment Interface {#implement-payment-interface}
17+
18+
Implement your payment interface according to the specifications in this chapter, deploy the interface, and ensure it can communicate with Cloudreve over the network.
19+
20+
::: warning Warning
21+
The custom payment API for version 3 is not compatible with version 4.
22+
:::
23+
24+
### Create Order `POST <your-payment-endpoint>` {#create-order}
25+
26+
When a new order is created, Cloudreve sends a request to your payment interface.
27+
28+
#### Request Headers {#request-headers-create-order}
29+
30+
| Name | Type | Description |
31+
| --------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
32+
| `Authorization` | `String` | A signature calculated using the `Communication key` you set in the backend. See [Verify Signature](#verify-signature) for details. |
33+
| `X-Cr-Version` | `String` | Cloudreve version number |
34+
| `X-Cr-Site-Id` | `String` | Cloudreve site ID, which can be used to distinguish different sites |
35+
| `X-Cr-Site-Url` | `String` | The main site URL of Cloudreve. |
36+
37+
#### Request Body {#request-body-create-order}
38+
39+
The request body is sent in JSON format and includes the following fields:
40+
41+
| Name | Type | Description |
42+
| ------------ | -------- | --------------------------------------------------- |
43+
| `name` | `String` | Payment name |
44+
| `order_no` | `String` | Order number |
45+
| `notify_url` | `String` | Callback notification URL |
46+
| `amount` | `Number` | Payment amount, using the smallest unit of currency |
47+
| `currency` | `String` | ISO 4217 code of the currency |
48+
49+
### Request Example {#request-example-create-order}
50+
51+
```http
52+
POST /order
53+
Host: examplepayment.com
54+
Authorization: Bearer Vep6hl1x8fiQLasEauMEUqxFKyEqSXb9D_BBQpOiTd8=:1676027218
55+
X-Cr-Site-Url: https://demo.cloudreve.org
56+
X-Cr-Site-Id: b7de8bba-8f86-40fe-8171-c2625b6c4a61
57+
X-Cr-Version: 4.0.0
58+
59+
{
60+
"name": "Unlimited Storage",
61+
"order_no": "20230209190648343421",
62+
"notify_url": "http://demo.cloudreve.org/api/v4/callback/custom/20230209190648343421",
63+
"amount": 8900,
64+
"currency": "CNY"
65+
}
66+
```
67+
68+
### Expected Response {#expected-response-create-order}
69+
70+
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.
71+
72+
:::tabs
73+
74+
=== Success
75+
76+
```http
77+
HTTP/1.1 200 OK
78+
79+
{
80+
// A successful response is always 0
81+
"code": 0,
82+
// 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
83+
"data": "https://examplepayment.com/checkout/26544743"
84+
}
85+
```
86+
87+
=== Failure
88+
89+
```http
90+
HTTP/1.1 200 OK
91+
92+
{
93+
// Any non-zero code indicates order creation failure
94+
"code": 500,
95+
// Detailed description of the error
96+
"error": "Failed to create a payment."
97+
}
98+
```
99+
100+
:::
101+
102+
### Query Order Status `GET <your-payment-endpoint>` {#query-order-status}
103+
104+
When Cloudreve needs to query the order status, it sends a request to your endpoint.
105+
106+
#### Request Headers {#request-headers-query-order-status}
107+
108+
| Name | Type | Description |
109+
| --------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
110+
| `Authorization` | `String` | A signature calculated using the `Communication key` you set in the backend. See [Verify Signature](#verify-signature) for details. |
111+
| `X-Cr-Version` | `String` | Cloudreve version number |
112+
| `X-Cr-Site-Id` | `String` | Cloudreve site ID, which can be used to distinguish different sites |
113+
| `X-Cr-Site-Url` | `String` | The main site URL of Cloudreve. |
114+
115+
#### Request Parameters {#request-parameters-query-order-status}
116+
117+
Request parameters are sent as URL parameters and include the following fields:
118+
119+
| Name | Type | Description |
120+
| ---------- | -------- | ------------ |
121+
| `order_no` | `String` | Order number |
122+
123+
### Request Example {#request-example-query-order-status}
124+
125+
```http
126+
GET /order?order_no=20230209190648343421
127+
Host: examplepayment.com
128+
Authorization: Bearer Vep6hl1x8fiQLasEauMEUqxFKyEqSXb9D_BBQpOiTd8=:1676027218
129+
X-Cr-Site-Url: https://demo.cloudreve.org
130+
X-Cr-Site-Id: b7de8bba-8f86-40fe-8171-c2625b6c4a61
131+
X-Cr-Version: 4.0.0
132+
```
133+
134+
### Expected Response {#expected-response-query-order-status}
135+
136+
:::tabs
137+
138+
=== Successfully Queried Order Status
139+
140+
```http
141+
HTTP/1.1 200 OK
142+
143+
{
144+
"code": 0,
145+
// PAID - Paid
146+
// Other values - Not paid
147+
"data": "PAID"
148+
}
149+
```
150+
151+
=== Other Errors
152+
153+
```http
154+
HTTP/1.1 200 OK
155+
156+
{
157+
"code": 500,
158+
"error": "Failed to query order status."
159+
}
160+
```
161+
162+
:::
163+
164+
## Verify Signature {#verify-signature}
165+
166+
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 Authorization header. You can verify this signature using the following algorithm:
167+
168+
1. Extract the part after Bearer in the Authorization value, split the string by `:`, and the second part is the expiration timestamp of the signature, noted as `timestamp`. Verify that it is greater than the current timestamp. The part before `:` is noted as `signature`;
169+
170+
2. Iterate over all request headers, filter out those prefixed with `X-Cr-`, convert them to `key=value` format, then sort and join the results with `&` to form the string `signedHeaderStr`.
171+
172+
```go
173+
var signedHeader []string
174+
for k, _ := range r.Header {
175+
if strings.HasPrefix(k, "X-Cr-") {
176+
signedHeader = append(signedHeader, fmt.Sprintf("%s=%s", k, r.Header.Get(k)))
177+
}
178+
}
179+
sort.Strings(signedHeader)
180+
signedHeaderStr := strings.Join(signedHeader, "&")
181+
```
182+
183+
3. Encode the request URL's `Path` part, request body, and `signedHeaderStr` as a JSON string `signContent`.
184+
185+
```go
186+
type RequestRawSign struct {
187+
Path string
188+
Header string
189+
Body string
190+
}
191+
192+
signContent, err := json.Marshal(RequestRawSign{
193+
Path: r.URL.Path,
194+
Header: signedHeaderStr,
195+
Body: string(r.Body),
196+
})
197+
```
198+
199+
4. Concatenate `signContent` and `timestamp` with `:` to form the string `signContentFinal`, and use the HMAC algorithm and `Communication key` to calculate the signature for `signContentFinal`, noted as `signActual`.
200+
201+
```go
202+
signContentFinal := fmt.Sprintf("%s:%s", signContent, timestamp)
203+
signActual := hmac.New(sha256.New, []byte(Communication key)).Sum([]byte(signContentFinal))
204+
```
205+
206+
5. Compare `signActual` with `signature` to check for consistency.
207+
208+
## Send Callback
209+
210+
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:
211+
212+
:::tabs
213+
214+
=== Success
215+
216+
```http
217+
HTTP/1.1 200 OK
218+
219+
{
220+
// A successful response is always 0
221+
"code": 0
222+
}
223+
```
224+
225+
=== Failure
226+
227+
```http
228+
HTTP/1.1 200 OK
229+
230+
{
231+
// Any non-zero code indicates callback failure
232+
"code": 500,
233+
// Detailed description of the error
234+
"error": "Failed to process callback."
235+
}
236+
```
237+
238+
:::

0 commit comments

Comments
 (0)