|
| 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