实名认证对接--ok
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
package realname
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const cloudMarketProviderName = "aliyun_cloudmarket"
|
||||
|
||||
type CloudMarketConfig struct {
|
||||
URL string
|
||||
AppCode string
|
||||
}
|
||||
|
||||
type CloudMarketProvider struct {
|
||||
url string
|
||||
appCode string
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
type cloudMarketResponse struct {
|
||||
RequestID string `json:"request_id"`
|
||||
Status string `json:"status"`
|
||||
Result json.RawMessage `json:"result"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
func NewCloudMarketProvider(cfg CloudMarketConfig) (*CloudMarketProvider, error) {
|
||||
endpoint := strings.TrimSpace(cfg.URL)
|
||||
appCode := strings.TrimSpace(cfg.AppCode)
|
||||
if endpoint == "" || appCode == "" {
|
||||
return nil, ErrProviderConfigInvalid
|
||||
}
|
||||
if _, err := url.ParseRequestURI(endpoint); err != nil {
|
||||
return nil, fmt.Errorf("realname cloud market url invalid: %w", err)
|
||||
}
|
||||
return &CloudMarketProvider{
|
||||
url: endpoint,
|
||||
appCode: appCode,
|
||||
client: &http.Client{Timeout: 10 * time.Second},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *CloudMarketProvider) Name() string {
|
||||
return cloudMarketProviderName
|
||||
}
|
||||
|
||||
func (p *CloudMarketProvider) Start(ctx context.Context, req StartRequest) (ProviderResult, error) {
|
||||
endpoint, err := url.Parse(p.url)
|
||||
if err != nil {
|
||||
return ProviderResult{}, err
|
||||
}
|
||||
query := endpoint.Query()
|
||||
query.Set("name", strings.TrimSpace(req.Name))
|
||||
query.Set("id_number", strings.TrimSpace(req.IDNo))
|
||||
endpoint.RawQuery = query.Encode()
|
||||
|
||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), nil)
|
||||
if err != nil {
|
||||
return ProviderResult{}, err
|
||||
}
|
||||
httpReq.Header.Set("Authorization", "APPCODE "+p.appCode)
|
||||
httpReq.Header.Set("Accept", "application/json")
|
||||
|
||||
resp, err := p.client.Do(httpReq)
|
||||
if err != nil {
|
||||
return ProviderResult{}, fmt.Errorf("realname cloud market request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode == http.StatusForbidden {
|
||||
return ProviderResult{}, ErrProviderRateLimited
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
||||
if err != nil {
|
||||
return ProviderResult{}, fmt.Errorf("%w: read cloud market response: %v", ErrProviderUnavailable, err)
|
||||
}
|
||||
|
||||
var payload cloudMarketResponse
|
||||
if err := json.Unmarshal(body, &payload); err != nil {
|
||||
return ProviderResult{}, fmt.Errorf("%w: invalid cloud market response: %v", ErrProviderUnavailable, err)
|
||||
}
|
||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||
return ProviderResult{}, cloudMarketHTTPError(resp.StatusCode, payload.Reason)
|
||||
}
|
||||
if strings.ToUpper(strings.TrimSpace(payload.Status)) != "OK" {
|
||||
return ProviderResult{
|
||||
ProviderOrderNo: payload.RequestID,
|
||||
Status: StatusRejected,
|
||||
MaskedName: maskName(req.Name),
|
||||
MaskedIDNo: maskIDNo(req.IDNo),
|
||||
FailReason: firstNonEmpty(payload.Reason, "实名认证请求失败"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
result, err := parseCloudMarketResult(payload.Result)
|
||||
if err != nil {
|
||||
return ProviderResult{}, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
base := ProviderResult{
|
||||
ProviderOrderNo: payload.RequestID,
|
||||
MaskedName: maskName(req.Name),
|
||||
MaskedIDNo: maskIDNo(req.IDNo),
|
||||
}
|
||||
switch result {
|
||||
case 1:
|
||||
base.Status = StatusVerified
|
||||
base.VerifiedAt = &now
|
||||
case 2:
|
||||
base.Status = StatusRejected
|
||||
base.FailReason = "姓名与身份证号不一致"
|
||||
case 3:
|
||||
base.Status = StatusRejected
|
||||
base.FailReason = "公安库无匹配记录"
|
||||
case 110:
|
||||
return ProviderResult{}, ErrProviderRateLimited
|
||||
default:
|
||||
base.Status = StatusRejected
|
||||
base.FailReason = fmt.Sprintf("未知核验结果:%d", result)
|
||||
}
|
||||
return base, nil
|
||||
}
|
||||
|
||||
func cloudMarketHTTPError(statusCode int, reason string) error {
|
||||
reason = strings.TrimSpace(reason)
|
||||
switch statusCode {
|
||||
case http.StatusBadRequest:
|
||||
return fmt.Errorf("%w: %s", ErrInvalidRealnameInput, firstNonEmpty(reason, "cloud market bad request"))
|
||||
case http.StatusUnauthorized, http.StatusNotFound:
|
||||
return fmt.Errorf("%w: cloud market http status %d: %s", ErrProviderConfigInvalid, statusCode, reason)
|
||||
default:
|
||||
return fmt.Errorf("%w: cloud market http status %d: %s", ErrProviderUnavailable, statusCode, reason)
|
||||
}
|
||||
}
|
||||
|
||||
func parseCloudMarketResult(raw json.RawMessage) (int, error) {
|
||||
if len(raw) == 0 || string(raw) == "null" {
|
||||
return 0, fmt.Errorf("realname cloud market result missing")
|
||||
}
|
||||
var number int
|
||||
if err := json.Unmarshal(raw, &number); err == nil {
|
||||
return number, nil
|
||||
}
|
||||
var text string
|
||||
if err := json.Unmarshal(raw, &text); err == nil {
|
||||
switch strings.TrimSpace(text) {
|
||||
case "1":
|
||||
return 1, nil
|
||||
case "2":
|
||||
return 2, nil
|
||||
case "3":
|
||||
return 3, nil
|
||||
case "110":
|
||||
return 110, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("realname cloud market result invalid: %s", string(raw))
|
||||
}
|
||||
|
||||
func firstNonEmpty(values ...string) string {
|
||||
for _, value := range values {
|
||||
value = strings.TrimSpace(value)
|
||||
if value != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user