534 lines
17 KiB
Go
534 lines
17 KiB
Go
package payment
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
bizpayment "kra/internal/biz/payment"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-pay/gopay"
|
|
gopayAlipay "github.com/go-pay/gopay/alipay"
|
|
gopayAlipayV3 "github.com/go-pay/gopay/alipay/v3"
|
|
"github.com/go-pay/gopay/pkg/xhttp"
|
|
)
|
|
|
|
type alipayV3Adapter struct{}
|
|
|
|
type alipayV3Client struct {
|
|
client *gopayAlipayV3.ClientV3
|
|
gateway *url.URL
|
|
}
|
|
|
|
func (a *alipayV3Adapter) Create(ctx context.Context, req *bizpayment.PaymentRequest, c map[string]any) (*bizpayment.PaymentResult, error) {
|
|
if req == nil {
|
|
return nil, errors.New("支付宝 V3 下单参数为空")
|
|
}
|
|
amount, err := formatDecimalAmount(req.Amount, 100)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sdk, err := newAlipayV3Client(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
method, err := alipayCreateMethod(req.Extra, c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bm := gopay.BodyMap{
|
|
"out_trade_no": req.TradeNo,
|
|
"total_amount": amount,
|
|
"subject": req.Subject,
|
|
}
|
|
protected := []string{
|
|
"out_trade_no", "total_amount", "subject", "notify_url", "return_url",
|
|
"method", "pay_method", "trade_type", "channel",
|
|
}
|
|
mergeGoPayConfigExtras(bm, c, "create_extra", protected...)
|
|
mergeMap(bm, req.Extra, protected...)
|
|
if req.NotifyURL != "" {
|
|
bm.Set("notify_url", req.NotifyURL)
|
|
}
|
|
if req.ReturnURL != "" {
|
|
bm.Set("return_url", req.ReturnURL)
|
|
}
|
|
|
|
switch method {
|
|
case "alipay.trade.pay":
|
|
if bm.GetString("scene") == "" {
|
|
bm.Set("scene", "bar_code")
|
|
}
|
|
rsp, callErr := sdk.client.TradePay(ctx, bm)
|
|
if callErr != nil {
|
|
return nil, callErr
|
|
}
|
|
if rsp == nil {
|
|
return nil, errors.New("支付宝 V3 付款码支付响应为空")
|
|
}
|
|
if err = alipayV3CheckResponse("付款码支付", rsp.StatusCode, rsp.ErrResponse); err != nil {
|
|
return nil, err
|
|
}
|
|
return alipayV3TradePayResult(req, rsp)
|
|
case "alipay.trade.precreate":
|
|
rsp, callErr := sdk.client.TradePrecreate(ctx, bm)
|
|
if callErr != nil {
|
|
return nil, callErr
|
|
}
|
|
if rsp == nil {
|
|
return nil, errors.New("支付宝 V3 预创建响应为空")
|
|
}
|
|
if err = alipayV3CheckResponse("预创建", rsp.StatusCode, rsp.ErrResponse); err != nil {
|
|
return nil, err
|
|
}
|
|
outTradeNo := strings.TrimSpace(rsp.OutTradeNo)
|
|
if outTradeNo == "" || outTradeNo != req.TradeNo {
|
|
return nil, errors.New("支付宝 V3 预创建响应的 out_trade_no 缺失或不匹配")
|
|
}
|
|
if strings.TrimSpace(rsp.QrCode) == "" {
|
|
return nil, errors.New("支付宝 V3 预创建响应缺少 qr_code")
|
|
}
|
|
return &bizpayment.PaymentResult{
|
|
Provider: bizpayment.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo,
|
|
QueryID: outTradeNo, Payload: mustJSON(rsp),
|
|
}, nil
|
|
case "alipay.trade.app.pay":
|
|
orderString, callErr := sdk.client.TradeAppPay(ctx, bm)
|
|
if callErr != nil {
|
|
return nil, callErr
|
|
}
|
|
if strings.TrimSpace(orderString) == "" {
|
|
return nil, errors.New("支付宝 V3 APP 支付参数为空")
|
|
}
|
|
return &bizpayment.PaymentResult{
|
|
Provider: bizpayment.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo,
|
|
Payload: mustJSON(map[string]string{"order_string": orderString}),
|
|
}, nil
|
|
case "alipay.trade.page.pay":
|
|
payURL, callErr := sdk.client.TradePagePay(ctx, bm)
|
|
if callErr != nil {
|
|
return nil, callErr
|
|
}
|
|
payURL, err = sdk.rewriteGeneratedURL(payURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if strings.TrimSpace(payURL) == "" {
|
|
return nil, errors.New("支付宝 V3 电脑网站支付 URL 为空")
|
|
}
|
|
return &bizpayment.PaymentResult{
|
|
Provider: bizpayment.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo,
|
|
Payload: mustJSON(map[string]string{"pay_url": payURL}),
|
|
}, nil
|
|
case "alipay.trade.wap.pay":
|
|
payURL, callErr := sdk.client.TradeWapPay(ctx, bm)
|
|
if callErr != nil {
|
|
return nil, callErr
|
|
}
|
|
payURL, err = sdk.rewriteGeneratedURL(payURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if strings.TrimSpace(payURL) == "" {
|
|
return nil, errors.New("支付宝 V3 手机网站支付 URL 为空")
|
|
}
|
|
return &bizpayment.PaymentResult{
|
|
Provider: bizpayment.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo,
|
|
Payload: mustJSON(map[string]string{"pay_url": payURL}),
|
|
}, nil
|
|
default:
|
|
if bm.GetString("product_code") == "" {
|
|
bm.Set("product_code", firstAnyOr(c, "JSAPI_PAY", "product_code"))
|
|
}
|
|
if bm.GetString("op_app_id") == "" {
|
|
bm.Set("op_app_id", firstAnyOr(c, text(c, "app_id"), "op_app_id"))
|
|
}
|
|
rsp, callErr := sdk.client.TradeCreate(ctx, bm)
|
|
if callErr != nil {
|
|
return nil, callErr
|
|
}
|
|
if rsp == nil {
|
|
return nil, errors.New("支付宝 V3 创建响应为空")
|
|
}
|
|
if err = alipayV3CheckResponse("创建", rsp.StatusCode, rsp.ErrResponse); err != nil {
|
|
return nil, err
|
|
}
|
|
outTradeNo := strings.TrimSpace(rsp.OutTradeNo)
|
|
if outTradeNo == "" || outTradeNo != req.TradeNo {
|
|
return nil, errors.New("支付宝 V3 创建响应的 out_trade_no 缺失或不匹配")
|
|
}
|
|
providerTradeNo := strings.TrimSpace(rsp.TradeNo)
|
|
if providerTradeNo == "" {
|
|
return nil, errors.New("支付宝 V3 创建响应缺少 trade_no")
|
|
}
|
|
return &bizpayment.PaymentResult{
|
|
Provider: bizpayment.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo,
|
|
ProviderTradeNo: providerTradeNo, QueryID: outTradeNo, Payload: mustJSON(rsp),
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
func (a *alipayV3Adapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*bizpayment.PaymentResult, error) {
|
|
tradeNo = strings.TrimSpace(tradeNo)
|
|
if tradeNo == "" {
|
|
return nil, errors.New("支付宝 V3 查单缺少 out_trade_no")
|
|
}
|
|
sdk, err := newAlipayV3Client(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rsp, callErr := sdk.client.TradeQuery(ctx, gopay.BodyMap{"out_trade_no": tradeNo})
|
|
if callErr != nil {
|
|
return nil, callErr
|
|
}
|
|
if rsp == nil {
|
|
return nil, errors.New("支付宝 V3 查询响应为空")
|
|
}
|
|
if err = alipayV3CheckResponse("查单", rsp.StatusCode, rsp.ErrResponse); err != nil {
|
|
return nil, err
|
|
}
|
|
outTradeNo := strings.TrimSpace(rsp.OutTradeNo)
|
|
if outTradeNo == "" || outTradeNo != tradeNo {
|
|
return nil, errors.New("支付宝 V3 查询响应的 out_trade_no 缺失或不匹配")
|
|
}
|
|
result := &bizpayment.PaymentResult{
|
|
Provider: bizpayment.PaymentAlipayV3, Status: normalizePaymentStatus(rsp.TradeStatus, "pending"),
|
|
TradeNo: outTradeNo, ProviderTradeNo: strings.TrimSpace(rsp.TradeNo),
|
|
Currency: alipayV3Currency(c, ""), Payload: mustJSON(rsp),
|
|
}
|
|
if result.Status != "success" {
|
|
return result, nil
|
|
}
|
|
if result.ProviderTradeNo == "" {
|
|
return nil, errors.New("支付宝 V3 查询响应缺少 trade_no")
|
|
}
|
|
result.Amount, err = parseDecimalAmount(rsp.TotalAmount, 100)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("解析支付宝 V3 订单金额: %w", err)
|
|
}
|
|
values, err := alipayResponseValues(rsp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err = populateAlipayBreakdown(result, values); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (a *alipayV3Adapter) Refund(ctx context.Context, req *bizpayment.PaymentRefundRequest, c map[string]any) (*bizpayment.PaymentResult, error) {
|
|
if req == nil {
|
|
return nil, errors.New("支付宝 V3 退款请求为空")
|
|
}
|
|
tradeNo := strings.TrimSpace(req.TradeNo)
|
|
refundNo := strings.TrimSpace(req.RefundNo)
|
|
if tradeNo == "" || refundNo == "" {
|
|
return nil, errors.New("支付宝 V3 退款缺少 out_trade_no 或 out_request_no")
|
|
}
|
|
amount, err := formatDecimalAmount(req.Amount, 100)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sdk, err := newAlipayV3Client(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bm := gopay.BodyMap{
|
|
"out_trade_no": tradeNo,
|
|
"out_request_no": refundNo,
|
|
"refund_amount": amount,
|
|
}
|
|
mergeGoPayConfigExtras(bm, c, "refund_extra", "out_trade_no", "out_request_no", "refund_amount")
|
|
rsp, callErr := sdk.client.TradeRefund(ctx, bm)
|
|
if callErr != nil {
|
|
return nil, callErr
|
|
}
|
|
if rsp == nil {
|
|
return nil, errors.New("支付宝 V3 退款响应为空")
|
|
}
|
|
if err = alipayV3CheckResponse("退款", rsp.StatusCode, rsp.ErrResponse); err != nil {
|
|
return nil, err
|
|
}
|
|
outTradeNo := strings.TrimSpace(rsp.OutTradeNo)
|
|
if outTradeNo == "" || outTradeNo != tradeNo {
|
|
return nil, errors.New("支付宝 V3 退款响应的 out_trade_no 缺失或不匹配")
|
|
}
|
|
providerTradeNo := strings.TrimSpace(rsp.TradeNo)
|
|
if providerTradeNo == "" {
|
|
return nil, errors.New("支付宝 V3 退款响应缺少 trade_no")
|
|
}
|
|
if expected := strings.TrimSpace(req.ProviderTradeNo); expected != "" && providerTradeNo != expected {
|
|
return nil, errors.New("支付宝 V3 退款响应的 trade_no 不匹配")
|
|
}
|
|
refundedAmount, err := parseDecimalAmount(rsp.RefundFee, 100)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("解析支付宝 V3 退款金额: %w", err)
|
|
}
|
|
if refundedAmount != req.Amount {
|
|
return nil, errors.New("支付宝 V3 退款响应金额不匹配")
|
|
}
|
|
return &bizpayment.PaymentResult{
|
|
Provider: bizpayment.PaymentAlipayV3, Status: "created", TradeNo: tradeNo,
|
|
ProviderTradeNo: providerTradeNo, Amount: refundedAmount,
|
|
Currency: alipayV3Currency(c, req.Currency), Payload: mustJSON(rsp),
|
|
}, nil
|
|
}
|
|
|
|
func (a *alipayV3Adapter) Callback(_ context.Context, callback *bizpayment.PaymentCallback, c map[string]any) (*bizpayment.PaymentResult, error) {
|
|
if callback == nil || len(callback.Body) == 0 {
|
|
return nil, errors.New("支付宝 V3 回调为空")
|
|
}
|
|
fields := callbackFields(callback)
|
|
if fields["sign"] == "" {
|
|
if values, parseErr := url.ParseQuery(string(callback.Body)); parseErr == nil {
|
|
for key, value := range values {
|
|
if len(value) > 0 {
|
|
fields[key] = value[0]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if err := verifyAlipayV3Callback(fields, c); err != nil {
|
|
return nil, err
|
|
}
|
|
if fields["app_id"] != text(c, "app_id") {
|
|
return nil, errors.New("支付宝 V3 回调 app_id 不匹配")
|
|
}
|
|
tradeNo := strings.TrimSpace(fields["out_trade_no"])
|
|
providerTradeNo := strings.TrimSpace(fields["trade_no"])
|
|
if tradeNo == "" || providerTradeNo == "" {
|
|
return nil, errors.New("支付宝 V3 回调缺少 out_trade_no 或 trade_no")
|
|
}
|
|
payload, _ := json.Marshal(fields)
|
|
return &bizpayment.PaymentResult{
|
|
Provider: bizpayment.PaymentAlipayV3, Status: normalizePaymentStatus(fields["trade_status"], "pending"),
|
|
TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, Payload: payload,
|
|
}, nil
|
|
}
|
|
|
|
func alipayV3TradePayResult(req *bizpayment.PaymentRequest, rsp *gopayAlipayV3.TradePayRsp) (*bizpayment.PaymentResult, error) {
|
|
if req == nil || rsp == nil {
|
|
return nil, errors.New("支付宝 V3 付款码支付响应为空")
|
|
}
|
|
outTradeNo := strings.TrimSpace(rsp.OutTradeNo)
|
|
if outTradeNo == "" || outTradeNo != req.TradeNo {
|
|
return nil, errors.New("支付宝 V3 付款码支付响应的 out_trade_no 缺失或不匹配")
|
|
}
|
|
providerTradeNo := strings.TrimSpace(rsp.TradeNo)
|
|
if providerTradeNo == "" {
|
|
return nil, errors.New("支付宝 V3 付款码支付响应缺少 trade_no")
|
|
}
|
|
amount, err := parseDecimalAmount(rsp.TotalAmount, 100)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("解析支付宝 V3 付款码支付金额: %w", err)
|
|
}
|
|
if amount != req.Amount {
|
|
return nil, errors.New("支付宝 V3 付款码支付响应金额不匹配")
|
|
}
|
|
result := &bizpayment.PaymentResult{
|
|
Provider: bizpayment.PaymentAlipayV3, Status: "success", TradeNo: outTradeNo,
|
|
ProviderTradeNo: providerTradeNo, Amount: amount,
|
|
Currency: strings.ToUpper(strings.TrimSpace(req.Currency)), Payload: mustJSON(rsp),
|
|
}
|
|
if result.Currency == "" {
|
|
result.Currency = "CNY"
|
|
}
|
|
values, err := alipayResponseValues(rsp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err = populateAlipayBreakdown(result, values); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func newAlipayV3Client(c map[string]any) (*alipayV3Client, error) {
|
|
appID := strings.TrimSpace(text(c, "app_id"))
|
|
privateKey, err := alipayV3PrivateKey(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if appID == "" {
|
|
return nil, errors.New("支付宝 V3 缺少 app_id")
|
|
}
|
|
production := !strings.EqualFold(strings.TrimSpace(text(c, "environment")), "sandbox")
|
|
client, err := gopayAlipayV3.NewClientV3(appID, goPayAlipayKey(privateKey, false), production)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
appCert, err := alipayV3AppCertificate(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rootCert, err := alipayV3RootCertificate(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
publicCert, err := alipayV3PublicCertificate(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err = client.SetCert(appCert, rootCert, publicCert); err != nil {
|
|
return nil, fmt.Errorf("配置支付宝 V3 证书: %w", err)
|
|
}
|
|
if token := strings.TrimSpace(text(c, "app_auth_token")); token != "" {
|
|
client.SetAppAuthToken(token)
|
|
}
|
|
client.SetHttpClient(xhttp.NewClient().SetTimeout(20 * time.Second).SetBodySize(8))
|
|
|
|
sdk := &alipayV3Client{client: client}
|
|
if endpoint := strings.TrimSpace(text(c, "gateway_url")); endpoint != "" {
|
|
sdk.gateway, err = url.Parse(endpoint)
|
|
if err != nil || sdk.gateway.Scheme == "" || sdk.gateway.Host == "" {
|
|
return nil, fmt.Errorf("支付宝 V3 gateway_url 无效: %s", endpoint)
|
|
}
|
|
}
|
|
apiEndpoint := strings.TrimSpace(firstAny(c, "api_base_url", "proxy_host", "base_url"))
|
|
if apiEndpoint == "" && sdk.gateway != nil && (sdk.gateway.Path == "" || sdk.gateway.Path == "/") {
|
|
apiEndpoint = sdk.gateway.String()
|
|
}
|
|
if apiEndpoint != "" {
|
|
parsed, parseErr := url.Parse(apiEndpoint)
|
|
if parseErr != nil || parsed.Scheme == "" || parsed.Host == "" {
|
|
return nil, fmt.Errorf("支付宝 V3 api_base_url 无效: %s", apiEndpoint)
|
|
}
|
|
client.SetProxyHost(strings.TrimRight(apiEndpoint, "/"))
|
|
}
|
|
return sdk, nil
|
|
}
|
|
|
|
func (c *alipayV3Client) rewriteGeneratedURL(value string) (string, error) {
|
|
if strings.TrimSpace(value) == "" || c.gateway == nil {
|
|
return value, nil
|
|
}
|
|
source, err := url.Parse(value)
|
|
if err != nil {
|
|
return "", fmt.Errorf("解析支付宝 V3 支付 URL: %w", err)
|
|
}
|
|
return alipayTargetURL(source, c.gateway).String(), nil
|
|
}
|
|
|
|
func verifyAlipayV3Callback(fields map[string]string, c map[string]any) error {
|
|
if text(c, "app_id") == "" || fields["sign"] == "" {
|
|
return errors.New("支付宝 V3 回调验签配置或 sign 缺失")
|
|
}
|
|
values := url.Values{}
|
|
for key, value := range fields {
|
|
values.Set(key, value)
|
|
}
|
|
body, err := gopayAlipay.ParseNotifyByURLValues(values)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
publicCert, err := alipayV3PublicCertificate(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ok, err := gopayAlipay.VerifySignWithCert(publicCert, cloneGoPayBodyMap(body))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !ok {
|
|
return errors.New("支付宝 V3 回调签名校验失败")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func alipayV3CheckResponse(operation string, status int, response gopayAlipayV3.ErrResponse) error {
|
|
if status == http.StatusOK {
|
|
return nil
|
|
}
|
|
message := strings.TrimSpace(response.Message)
|
|
if code := strings.TrimSpace(response.Code); code != "" {
|
|
if message == "" {
|
|
message = code
|
|
} else {
|
|
message = code + ": " + message
|
|
}
|
|
}
|
|
if message == "" {
|
|
return fmt.Errorf("支付宝 V3 %s HTTP %d", operation, status)
|
|
}
|
|
return fmt.Errorf("支付宝 V3 %s HTTP %d: %s", operation, status, message)
|
|
}
|
|
|
|
func alipayV3Currency(c map[string]any, fallback string) string {
|
|
currency := strings.ToUpper(strings.TrimSpace(firstAny(c, "currency", "default_currency")))
|
|
if currency == "" {
|
|
currency = strings.ToUpper(strings.TrimSpace(fallback))
|
|
}
|
|
if currency == "" {
|
|
currency = "CNY"
|
|
}
|
|
return currency
|
|
}
|
|
|
|
func alipayV3PrivateKey(c map[string]any) (string, error) {
|
|
if value := strings.TrimSpace(firstAny(c, "private_key_content", "private_key")); value != "" {
|
|
return value, nil
|
|
}
|
|
path := strings.TrimSpace(text(c, "private_key_path"))
|
|
if path == "" {
|
|
return "", errors.New("支付宝 V3 缺少 private_key")
|
|
}
|
|
content, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return "", fmt.Errorf("读取支付宝 V3 private_key: %w", err)
|
|
}
|
|
return string(content), nil
|
|
}
|
|
|
|
func alipayV3AppCertificate(c map[string]any) ([]byte, error) {
|
|
return alipayV3Certificate(c, "app_cert",
|
|
[]string{"app_cert_content", "app_public_cert_content"},
|
|
[]string{"app_cert_path", "app_public_cert_path"},
|
|
[]string{"app_cert", "app_public_cert"},
|
|
)
|
|
}
|
|
|
|
func alipayV3RootCertificate(c map[string]any) ([]byte, error) {
|
|
return alipayV3Certificate(c, "root_cert",
|
|
[]string{"alipay_root_cert_content", "root_cert_content"},
|
|
[]string{"alipay_root_cert_path", "root_cert_path"},
|
|
[]string{"alipay_root_cert", "root_cert"},
|
|
)
|
|
}
|
|
|
|
func alipayV3PublicCertificate(c map[string]any) ([]byte, error) {
|
|
return alipayV3Certificate(c, "public_cert",
|
|
[]string{"alipay_public_cert_content", "public_cert_content"},
|
|
[]string{"alipay_public_cert_path", "public_cert_path"},
|
|
[]string{"alipay_public_cert", "public_cert"},
|
|
)
|
|
}
|
|
|
|
func alipayV3Certificate(c map[string]any, label string, contentKeys, pathKeys, flexibleKeys []string) ([]byte, error) {
|
|
if value := strings.TrimSpace(firstAny(c, contentKeys...)); value != "" {
|
|
return []byte(value), nil
|
|
}
|
|
if path := strings.TrimSpace(firstAny(c, pathKeys...)); path != "" {
|
|
content, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("读取支付宝 V3 %s: %w", label, err)
|
|
}
|
|
return content, nil
|
|
}
|
|
if value := strings.TrimSpace(firstAny(c, flexibleKeys...)); value != "" {
|
|
if strings.Contains(value, "-----BEGIN CERTIFICATE-----") {
|
|
return []byte(value), nil
|
|
}
|
|
content, err := os.ReadFile(value)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("读取支付宝 V3 %s: %w", label, err)
|
|
}
|
|
return content, nil
|
|
}
|
|
return nil, fmt.Errorf("支付宝 V3 缺少 %s", label)
|
|
}
|