cURL
curl -X POST "${BASE_URL}/oauth/token" \
-H "Accept: application/json" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=${CLIENT_ID}" \
--data-urlencode "client_secret=${CLIENT_SECRET}"import json
import os
import urllib.parse
import urllib.request
base_url = os.environ["BASE_URL"].rstrip("/")
url = base_url + "/oauth/token"
headers = {"Accept": "application/json"}
params = {
"grant_type": "client_credentials",
"client_id": os.environ["CLIENT_ID"],
"client_secret": os.environ["CLIENT_SECRET"],
}
body = urllib.parse.urlencode(params).encode("utf-8")
headers["Content-Type"] = "application/x-www-form-urlencoded"
request = urllib.request.Request(url, data=body, headers=headers, method="POST")
with urllib.request.urlopen(request, timeout=30) as response:
print(response.read().decode("utf-8"))
const env = (name) => {
const value = process.env[name];
if (!value) throw new Error(`Missing environment variable: ${name}`);
return value;
};
const baseUrl = env("BASE_URL").replace(/\/+$/, "");
const url = new URL(baseUrl + "/oauth/token");
const headers = { Accept: "application/json" };
const params = new URLSearchParams({
"grant_type": "client_credentials",
"client_id": env("CLIENT_ID"),
"client_secret": env("CLIENT_SECRET"),
});
const body = params;
headers["Content-Type"] = "application/x-www-form-urlencoded";
const response = await fetch(url, {
method: "POST",
headers,
body,
signal: AbortSignal.timeout(30_000),
});
console.log(await response.text());
<?php
$env = static function (string $name): string {
$value = getenv($name);
if ($value === false || $value === '') {
throw new RuntimeException("Missing environment variable: " . $name);
}
return $value;
};
$baseUrl = rtrim($env('BASE_URL'), '/');
$url = $baseUrl . "/oauth/token";
$headers = ['Accept: application/json'];
$params = [
"grant_type" => "client_credentials",
"client_id" => $env("CLIENT_ID"),
"client_secret" => $env("CLIENT_SECRET"),
];
$body = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
$curl = curl_init($url);
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($curl);
if ($response === false) {
$error = curl_error($curl);
curl_close($curl);
throw new RuntimeException($error);
}
curl_close($curl);
echo $response, PHP_EOL;
package main
import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
"time"
)
func mustEnv(name string) string {
value := os.Getenv(name)
if value == "" { panic("Missing environment variable: " + name) }
return value
}
func main() {
baseURL := strings.TrimRight(mustEnv("BASE_URL"), "/")
endpoint := baseURL + "/oauth/token"
params := url.Values{}
params.Set("grant_type", "client_credentials")
params.Set("client_id", mustEnv("CLIENT_ID"))
params.Set("client_secret", mustEnv("CLIENT_SECRET"))
body := strings.NewReader(params.Encode())
req, err := http.NewRequest("POST", endpoint, body)
if err != nil { panic(err) }
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
client := &http.Client{Timeout: 30 * time.Second}
response, err := client.Do(req)
if err != nil { panic(err) }
defer response.Body.Close()
result, err := io.ReadAll(response.Body)
if err != nil { panic(err) }
fmt.Println(string(result))
}
import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
public class Example {
static String requireEnv(String name) {
String value = System.getenv(name);
if (value == null || value.isEmpty()) {
throw new IllegalArgumentException("Missing environment variable: " + name);
}
return value;
}
public static void main(String[] args) throws Exception {
String baseUrl = requireEnv("BASE_URL").replaceAll("/+$", "");
String endpoint = baseUrl + "/oauth/token";
Map<String, String> params = new LinkedHashMap<>();
params.put("grant_type", "client_credentials");
params.put("client_id", requireEnv("CLIENT_ID"));
params.put("client_secret", requireEnv("CLIENT_SECRET"));
String encoded = params.entrySet().stream()
.map(e -> URLEncoder.encode(e.getKey(), StandardCharsets.UTF_8) + "="
+ URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))
.collect(Collectors.joining("&"));
HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(endpoint))
.timeout(Duration.ofSeconds(30))
.header("Accept", "application/json");
builder.header("Content-Type", "application/x-www-form-urlencoded");
HttpRequest request = builder.method("POST", HttpRequest.BodyPublishers.ofString(encoded, StandardCharsets.UTF_8)).build();
HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(30)).build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
System.out.println(response.body());
}
}
require "net/http"
require "uri"
base_url = ENV.fetch("BASE_URL").sub(%r{/+$}, "")
uri = URI(base_url + "/oauth/token")
params = {
"grant_type" => "client_credentials",
"client_id" => ENV.fetch("CLIENT_ID"),
"client_secret" => ENV.fetch("CLIENT_SECRET"),
}
request = Net::HTTP::Post.new(uri)
request["Accept"] = "application/json"
request.set_form_data(params)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == "https"
http.open_timeout = 30
http.read_timeout = 30
puts http.request(request).body
{
"token_type": "Bearer",
"expires_in": 7200,
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiI4YTZiZjJkOS00N2MxLTRlODUtOTJiMy02ZDFhN2MwODRmNTIiLCJqdGkiOiJhZGRiNDgzNDkxMGM2MThjZmJiNGRhMTJhZDMwNWEwNGU3ZmZiNWUzMTE3YjI3YmQwNTVlMjk5ZTgzOTU1ZTkyIiwiaWF0IjoxODkzNDUyNDAwLCJuYmYiOjE4OTM0NTI0MDAsImV4cCI6MTg5MzQ1OTYwMCwic3ViIjoiOGE2YmYyZDktNDdjMS00ZTg1LTkyYjMtNmQxYTdjMDg0ZjUyIiwic2NvcGVzIjpbXX0.dLrN3edou1KGfN5a2kbfDRtGA-iM4meEfwlMTPc1UHv1wGrv70ue8Mb3UU0TYxbZHxK-kHHUVH9aWwItbsyYBkou3Sgr5NAwejvuxSOmQZHk4dT-Uw982pelDqBrBmJV5qZ5qY7qeGDc0Fz_0ymre50BLOd2d3MsLj-INdwjNNIF4fr2AG9M6mDK8pEqGMNMqT20GGVEQ1YJZGwpy-XiPp-6sVS3qOjyineqTK_hN3N0QXykoEM4iFGK_wQdc681B8tfXJRpcQksNt4A1pM2G5nUdnBC-ahByGYUaij4JAkxR_LpnXoR1xQbd-j4wj8cG78WAPdJTzZcIFPRU0Gmjw"
}{
"error": "invalid_request",
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.",
"hint": "Check the `grant_type` parameter"
}{
"error": "invalid_client",
"error_description": "Client authentication failed"
}{
"message": "Too Many Attempts."
}认证
获取 OAuth 访问令牌
使用 OAuth2 client_credentials 获取访问令牌。Client 凭据须已获准使用、处于启用状态、未撤销且未过期。如配置 IP 白名单,业务请求须来自获准 IP。Client Secret 与 access_token 仅在商户服务端使用。令牌到期后重新申请,不提供 refresh_token。获取令牌每分钟最多 60 次,超过限制返回 HTTP 429。
POST
/
oauth
/
token
cURL
curl -X POST "${BASE_URL}/oauth/token" \
-H "Accept: application/json" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=${CLIENT_ID}" \
--data-urlencode "client_secret=${CLIENT_SECRET}"import json
import os
import urllib.parse
import urllib.request
base_url = os.environ["BASE_URL"].rstrip("/")
url = base_url + "/oauth/token"
headers = {"Accept": "application/json"}
params = {
"grant_type": "client_credentials",
"client_id": os.environ["CLIENT_ID"],
"client_secret": os.environ["CLIENT_SECRET"],
}
body = urllib.parse.urlencode(params).encode("utf-8")
headers["Content-Type"] = "application/x-www-form-urlencoded"
request = urllib.request.Request(url, data=body, headers=headers, method="POST")
with urllib.request.urlopen(request, timeout=30) as response:
print(response.read().decode("utf-8"))
const env = (name) => {
const value = process.env[name];
if (!value) throw new Error(`Missing environment variable: ${name}`);
return value;
};
const baseUrl = env("BASE_URL").replace(/\/+$/, "");
const url = new URL(baseUrl + "/oauth/token");
const headers = { Accept: "application/json" };
const params = new URLSearchParams({
"grant_type": "client_credentials",
"client_id": env("CLIENT_ID"),
"client_secret": env("CLIENT_SECRET"),
});
const body = params;
headers["Content-Type"] = "application/x-www-form-urlencoded";
const response = await fetch(url, {
method: "POST",
headers,
body,
signal: AbortSignal.timeout(30_000),
});
console.log(await response.text());
<?php
$env = static function (string $name): string {
$value = getenv($name);
if ($value === false || $value === '') {
throw new RuntimeException("Missing environment variable: " . $name);
}
return $value;
};
$baseUrl = rtrim($env('BASE_URL'), '/');
$url = $baseUrl . "/oauth/token";
$headers = ['Accept: application/json'];
$params = [
"grant_type" => "client_credentials",
"client_id" => $env("CLIENT_ID"),
"client_secret" => $env("CLIENT_SECRET"),
];
$body = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
$curl = curl_init($url);
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($curl);
if ($response === false) {
$error = curl_error($curl);
curl_close($curl);
throw new RuntimeException($error);
}
curl_close($curl);
echo $response, PHP_EOL;
package main
import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
"time"
)
func mustEnv(name string) string {
value := os.Getenv(name)
if value == "" { panic("Missing environment variable: " + name) }
return value
}
func main() {
baseURL := strings.TrimRight(mustEnv("BASE_URL"), "/")
endpoint := baseURL + "/oauth/token"
params := url.Values{}
params.Set("grant_type", "client_credentials")
params.Set("client_id", mustEnv("CLIENT_ID"))
params.Set("client_secret", mustEnv("CLIENT_SECRET"))
body := strings.NewReader(params.Encode())
req, err := http.NewRequest("POST", endpoint, body)
if err != nil { panic(err) }
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
client := &http.Client{Timeout: 30 * time.Second}
response, err := client.Do(req)
if err != nil { panic(err) }
defer response.Body.Close()
result, err := io.ReadAll(response.Body)
if err != nil { panic(err) }
fmt.Println(string(result))
}
import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
public class Example {
static String requireEnv(String name) {
String value = System.getenv(name);
if (value == null || value.isEmpty()) {
throw new IllegalArgumentException("Missing environment variable: " + name);
}
return value;
}
public static void main(String[] args) throws Exception {
String baseUrl = requireEnv("BASE_URL").replaceAll("/+$", "");
String endpoint = baseUrl + "/oauth/token";
Map<String, String> params = new LinkedHashMap<>();
params.put("grant_type", "client_credentials");
params.put("client_id", requireEnv("CLIENT_ID"));
params.put("client_secret", requireEnv("CLIENT_SECRET"));
String encoded = params.entrySet().stream()
.map(e -> URLEncoder.encode(e.getKey(), StandardCharsets.UTF_8) + "="
+ URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))
.collect(Collectors.joining("&"));
HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(endpoint))
.timeout(Duration.ofSeconds(30))
.header("Accept", "application/json");
builder.header("Content-Type", "application/x-www-form-urlencoded");
HttpRequest request = builder.method("POST", HttpRequest.BodyPublishers.ofString(encoded, StandardCharsets.UTF_8)).build();
HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(30)).build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
System.out.println(response.body());
}
}
require "net/http"
require "uri"
base_url = ENV.fetch("BASE_URL").sub(%r{/+$}, "")
uri = URI(base_url + "/oauth/token")
params = {
"grant_type" => "client_credentials",
"client_id" => ENV.fetch("CLIENT_ID"),
"client_secret" => ENV.fetch("CLIENT_SECRET"),
}
request = Net::HTTP::Post.new(uri)
request["Accept"] = "application/json"
request.set_form_data(params)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == "https"
http.open_timeout = 30
http.read_timeout = 30
puts http.request(request).body
{
"token_type": "Bearer",
"expires_in": 7200,
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiI4YTZiZjJkOS00N2MxLTRlODUtOTJiMy02ZDFhN2MwODRmNTIiLCJqdGkiOiJhZGRiNDgzNDkxMGM2MThjZmJiNGRhMTJhZDMwNWEwNGU3ZmZiNWUzMTE3YjI3YmQwNTVlMjk5ZTgzOTU1ZTkyIiwiaWF0IjoxODkzNDUyNDAwLCJuYmYiOjE4OTM0NTI0MDAsImV4cCI6MTg5MzQ1OTYwMCwic3ViIjoiOGE2YmYyZDktNDdjMS00ZTg1LTkyYjMtNmQxYTdjMDg0ZjUyIiwic2NvcGVzIjpbXX0.dLrN3edou1KGfN5a2kbfDRtGA-iM4meEfwlMTPc1UHv1wGrv70ue8Mb3UU0TYxbZHxK-kHHUVH9aWwItbsyYBkou3Sgr5NAwejvuxSOmQZHk4dT-Uw982pelDqBrBmJV5qZ5qY7qeGDc0Fz_0ymre50BLOd2d3MsLj-INdwjNNIF4fr2AG9M6mDK8pEqGMNMqT20GGVEQ1YJZGwpy-XiPp-6sVS3qOjyineqTK_hN3N0QXykoEM4iFGK_wQdc681B8tfXJRpcQksNt4A1pM2G5nUdnBC-ahByGYUaij4JAkxR_LpnXoR1xQbd-j4wj8cG78WAPdJTzZcIFPRU0Gmjw"
}{
"error": "invalid_request",
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.",
"hint": "Check the `grant_type` parameter"
}{
"error": "invalid_client",
"error_description": "Client authentication failed"
}{
"message": "Too Many Attempts."
}请求体
application/x-www-form-urlencoded
OAuth 表单。
授权方式,固定 client_credentials。
可用选项:
client_credentials Minimum string length:
1BillBuddy 分配的 OAuth Client ID,以字符串提交。
Minimum string length:
1OAuth Client Secret,仅在商户服务端保存。
Minimum string length:
1可选,以空格分隔的授权范围;通常省略或使用空字符串。