kra-new/app/system/internal/integration/payment/paypal.go

782 lines
23 KiB
Go

package payment
import (
"context"
"encoding/json"
"errors"
"fmt"
"math"
"strconv"
"strings"
"kra/app/system/internal/biz"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/paypal"
)
type paypalAdapter struct{}
func (a *paypalAdapter) client(c map[string]any) (*paypal.Client, error) {
environment := strings.ToLower(strings.TrimSpace(text(c, "environment")))
isProd := environment != "sandbox"
options := []paypal.Option{paypal.WithoutAutoRefreshToken()}
if baseURL := strings.TrimRight(firstAny(c, "api_base_url", "base_url", "proxy_url"), "/"); baseURL != "" {
options = append(options, paypal.WithProxyUrl(baseURL, baseURL))
}
client, err := paypal.NewClient(text(c, "client_id"), text(c, "client_secret"), isProd, options...)
if err != nil {
return nil, fmt.Errorf("PayPal 客户端初始化失败: %w", err)
}
client.SetRequestHeader("Prefer", "return=representation")
return client, nil
}
func (a *paypalAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) {
if req == nil {
return nil, errors.New("PayPal 下单参数为空")
}
client, err := a.client(c)
if err != nil {
return nil, err
}
currency := strings.ToUpper(strings.TrimSpace(req.Currency))
value, err := paypalFormatAmount(req.Amount, currency, c)
if err != nil {
return nil, err
}
purchaseUnit := &paypal.PurchaseUnit{
ReferenceId: req.TradeNo,
Description: req.Subject,
CustomId: req.TradeNo,
InvoiceId: req.TradeNo,
Amount: &paypal.Amount{CurrencyCode: currency, Value: value},
}
bm := gopay.BodyMap{
"intent": "CAPTURE",
"purchase_units": []*paypal.PurchaseUnit{purchaseUnit},
}
if applicationContext := paypalApplicationContext(req, c); len(applicationContext) > 0 {
bm["application_context"] = applicationContext
}
mergeGoPayExtras(bm, req.Extra, "intent", "purchase_units", "application_context")
client.SetRequestHeader("PayPal-Request-Id", req.TradeNo)
rsp, err := client.CreateOrder(ctx, bm)
if err != nil {
return nil, err
}
if rsp == nil || rsp.Code != paypal.Success || rsp.Response == nil {
return nil, paypalResponseError("下单", paypalResponseCode(rsp), paypalCreateError(rsp))
}
order := rsp.Response
if strings.TrimSpace(order.Id) == "" {
return nil, errors.New("PayPal 下单成功但缺少 order ID")
}
return paypalCreateResult(req, order, c)
}
func (a *paypalAdapter) Query(ctx context.Context, orderID string, c map[string]any) (*biz.PaymentResult, error) {
orderID = strings.TrimSpace(orderID)
if orderID == "" {
return nil, errors.New("PayPal 查单缺少 order ID")
}
client, err := a.client(c)
if err != nil {
return nil, err
}
rsp, err := client.OrderDetail(ctx, orderID, nil)
if err != nil {
return nil, err
}
if rsp == nil || rsp.Code != paypal.Success || rsp.Response == nil {
return nil, paypalResponseError("查单", paypalResponseCode(rsp), paypalOrderError(rsp))
}
if rsp.Response.Id != "" && rsp.Response.Id != orderID {
return nil, errors.New("PayPal 查单响应的 order ID 不匹配")
}
order := rsp.Response
if _, err = paypalOrderIdentity(order); err != nil {
return nil, err
}
if paypalAutoCaptureEnabled(c) && strings.EqualFold(strings.TrimSpace(order.Status), "APPROVED") {
order, err = paypalCaptureOrder(ctx, client, orderID, c)
if err != nil {
return nil, err
}
}
return paypalOrderResult(order, orderID, "", c)
}
// paypalCaptureOrder completes an order after the buyer approves it. PayPal's
// CAPTURE intent deliberately separates approval from settlement; treating an
// APPROVED order as paid would allow fulfillment before funds are captured.
func paypalCaptureOrder(ctx context.Context, client *paypal.Client, orderID string, c map[string]any) (*paypal.OrderDetail, error) {
if client == nil || strings.TrimSpace(orderID) == "" {
return nil, errors.New("PayPal 捕获订单参数为空")
}
bm := gopay.BodyMap{}
mergeGoPayConfigExtras(bm, c, "capture_extra")
client.SetRequestHeader("PayPal-Request-Id", strings.TrimSpace(orderID)+"-capture")
rsp, err := client.OrderCapture(ctx, orderID, bm)
if err != nil {
return nil, err
}
if rsp == nil || rsp.Code != paypal.Success || rsp.Response == nil {
return nil, paypalResponseError("捕获订单", paypalResponseCode(rsp), paypalCaptureError(rsp))
}
if returnedID := strings.TrimSpace(rsp.Response.Id); returnedID != "" && returnedID != strings.TrimSpace(orderID) {
return nil, errors.New("PayPal 捕获响应的 order ID 不匹配")
}
return rsp.Response, nil
}
func paypalAutoCaptureEnabled(c map[string]any) bool {
value, ok := c["auto_capture"]
if !ok || value == nil {
return true
}
switch parsed := value.(type) {
case bool:
return parsed
case string:
if result, err := strconv.ParseBool(strings.TrimSpace(parsed)); err == nil {
return result
}
case json.Number:
return parsed != "0"
case float64:
return parsed != 0
case int:
return parsed != 0
case int64:
return parsed != 0
}
return true
}
func (a *paypalAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) {
if req == nil {
return nil, errors.New("PayPal 退款请求为空")
}
tradeNo := strings.TrimSpace(req.TradeNo)
refundNo := strings.TrimSpace(req.RefundNo)
if tradeNo == "" || refundNo == "" || req.Amount <= 0 {
return nil, errors.New("PayPal 退款缺少商户订单号、退款单号或有效金额")
}
effective := *req
effective.TradeNo = tradeNo
effective.RefundNo = refundNo
req = &effective
client, err := a.client(c)
if err != nil {
return nil, err
}
captureID, err := paypalRefundCaptureID(ctx, client, req)
if err != nil {
return nil, err
}
currency := strings.ToUpper(strings.TrimSpace(req.Currency))
value, err := paypalFormatAmount(req.Amount, currency, c)
if err != nil {
return nil, err
}
bm := gopay.BodyMap{
"invoice_id": refundNo,
"amount": &paypal.Amount{
CurrencyCode: currency,
Value: value,
},
}
if note := strings.TrimSpace(text(c, "refund_note")); note != "" {
bm["note_to_payer"] = note
}
mergeGoPayConfigExtras(bm, c, "refund_extra", "amount", "invoice_id", "capture_id", "order_id", "query_id", "provider_trade_no")
client.SetRequestHeader("PayPal-Request-Id", refundNo)
rsp, err := client.PaymentCaptureRefund(ctx, captureID, bm)
if err != nil {
return nil, err
}
if rsp == nil || rsp.Code != paypal.Success || rsp.Response == nil {
return nil, paypalResponseError("退款", paypalResponseCode(rsp), paypalRefundError(rsp))
}
return paypalRefundResult(req, rsp.Response, c)
}
func paypalRefundResult(req *biz.PaymentRefundRequest, refund *paypal.PaymentCaptureRefund, c map[string]any) (*biz.PaymentResult, error) {
if req == nil || refund == nil {
return nil, errors.New("PayPal 退款响应为空")
}
if invoiceID := strings.TrimSpace(refund.InvoiceId); invoiceID == "" {
return nil, errors.New("PayPal 退款响应缺少 invoice_id")
} else if invoiceID != strings.TrimSpace(req.RefundNo) {
return nil, errors.New("PayPal 退款响应的 invoice_id 不匹配")
}
providerRefundID := strings.TrimSpace(refund.Id)
if providerRefundID == "" {
return nil, errors.New("PayPal 退款响应缺少 refund ID")
}
currency := strings.ToUpper(strings.TrimSpace(req.Currency))
result := &biz.PaymentResult{
Provider: biz.PaymentPayPal,
Status: normalizePayPalRefundState(refund.Status),
TradeNo: req.TradeNo,
ProviderTradeNo: providerRefundID,
Amount: req.Amount,
Currency: currency,
Payload: mustJSON(refund),
}
if refund.Amount == nil {
return nil, errors.New("PayPal 退款响应缺少金额")
}
parsed, err := paypalParseAmount(refund.Amount, c)
if err != nil {
return nil, err
}
if parsed != req.Amount {
return nil, errors.New("PayPal 退款响应金额不匹配")
}
responseCurrency := strings.ToUpper(strings.TrimSpace(refund.Amount.CurrencyCode))
if responseCurrency == "" {
return nil, errors.New("PayPal 退款响应缺少币种")
}
if currency != "" && responseCurrency != currency {
return nil, errors.New("PayPal 退款响应币种不匹配")
}
result.Amount = parsed
result.Currency = responseCurrency
return result, nil
}
func paypalCreateResult(req *biz.PaymentRequest, order *paypal.OrderDetail, c map[string]any) (*biz.PaymentResult, error) {
if req == nil || order == nil {
return nil, errors.New("PayPal 下单响应为空")
}
tradeNo, err := paypalOrderIdentity(order)
if err != nil {
return nil, err
}
if tradeNo != "" && tradeNo != strings.TrimSpace(req.TradeNo) {
return nil, errors.New("PayPal 下单响应的商户订单号不匹配")
}
amount, currency, found, err := paypalOrderAmount(order, c)
if err != nil {
return nil, err
}
if found {
if amount != req.Amount {
return nil, errors.New("PayPal 下单响应金额不匹配")
}
if expected := strings.ToUpper(strings.TrimSpace(req.Currency)); currency == "" || currency != expected {
return nil, errors.New("PayPal 下单响应币种不匹配")
}
}
result, err := paypalOrderResult(order, order.Id, req.TradeNo, c)
if err != nil {
return nil, err
}
result.TradeNo = req.TradeNo
return result, nil
}
func (a *paypalAdapter) Callback(ctx context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) {
if callback == nil || len(callback.Body) == 0 {
return nil, errors.New("PayPal 回调为空")
}
webhookID := strings.TrimSpace(text(c, "webhook_id"))
if webhookID == "" {
return nil, errors.New("PayPal 回调验签缺少 webhook_id")
}
var event paypal.WebhookEvent
if err := json.Unmarshal(callback.Body, &event); err != nil {
return nil, fmt.Errorf("PayPal 回调 JSON 无效: %w", err)
}
if strings.TrimSpace(event.Id) == "" || len(event.Resource) == 0 {
return nil, errors.New("PayPal 回调事件不完整")
}
var webhookEvent any
if err := json.Unmarshal(callback.Body, &webhookEvent); err != nil {
return nil, err
}
verifyBody := gopay.BodyMap{
"auth_algo": paypalCallbackHeader(callback, "Paypal-Auth-Algo"),
"cert_url": paypalCallbackHeader(callback, "Paypal-Cert-Url"),
"transmission_id": paypalCallbackHeader(callback, "Paypal-Transmission-Id"),
"transmission_sig": paypalCallbackHeader(callback, "Paypal-Transmission-Sig"),
"transmission_time": paypalCallbackHeader(callback, "Paypal-Transmission-Time"),
"webhook_id": webhookID,
"webhook_event": webhookEvent,
}
client, err := a.client(c)
if err != nil {
return nil, err
}
verified, err := client.VerifyWebhookSignature(ctx, verifyBody)
if err != nil {
return nil, err
}
if verified == nil || !strings.EqualFold(verified.VerificationStatus, "SUCCESS") {
return nil, errors.New("PayPal 回调签名校验失败")
}
result, err := paypalWebhookResult(&event)
if err != nil {
return nil, err
}
result.Payload = append([]byte(nil), callback.Body...)
result.EventID = event.Id
return result, nil
}
func paypalApplicationContext(req *biz.PaymentRequest, c map[string]any) map[string]any {
applicationContext := map[string]any{}
if configured, ok := req.Extra["application_context"].(map[string]any); ok {
for key, value := range configured {
applicationContext[key] = value
}
}
for _, key := range []string{"brand_name", "locale", "landing_page", "shipping_preference", "user_action"} {
if _, exists := applicationContext[key]; !exists {
if value := strings.TrimSpace(text(c, key)); value != "" {
applicationContext[key] = value
}
}
}
if returnURL := strings.TrimSpace(req.ReturnURL); returnURL != "" {
applicationContext["return_url"] = returnURL
}
cancelURL := firstAny(c, "cancel_url", "return_url")
if cancelURL == "" {
cancelURL = strings.TrimSpace(req.ReturnURL)
}
if cancelURL != "" {
applicationContext["cancel_url"] = cancelURL
}
return applicationContext
}
func paypalOrderResult(order *paypal.OrderDetail, queryID, fallbackTradeNo string, c map[string]any) (*biz.PaymentResult, error) {
if order == nil {
return nil, errors.New("PayPal 订单结果为空")
}
tradeNo, err := paypalOrderIdentity(order)
if err != nil {
return nil, err
}
if tradeNo == "" {
tradeNo = fallbackTradeNo
}
capture := paypalOrderCapture(order)
result := &biz.PaymentResult{
Provider: biz.PaymentPayPal,
Status: paypalOrderCaptureState(order),
TradeNo: tradeNo,
QueryID: queryID,
Payload: mustJSON(order),
}
if capture != nil {
result.ProviderTradeNo = capture.Id
}
amount, currency, found, err := paypalOrderAmount(order, c)
if err != nil {
return nil, err
}
if found {
result.Amount = amount
result.Currency = currency
}
return result, nil
}
func paypalOrderTradeNo(order *paypal.OrderDetail) string {
tradeNo, _ := paypalOrderIdentity(order)
return tradeNo
}
func paypalOrderIdentity(order *paypal.OrderDetail) (string, error) {
if order == nil {
return "", nil
}
identity := ""
for _, unit := range order.PurchaseUnits {
if unit == nil {
continue
}
for _, value := range []string{unit.InvoiceId, unit.CustomId, unit.ReferenceId} {
value = strings.TrimSpace(value)
if value == "" {
continue
}
if identity == "" {
identity = value
continue
}
if value != identity {
return "", errors.New("PayPal 订单响应包含冲突的商户订单号")
}
}
}
return identity, nil
}
func paypalOrderCapture(order *paypal.OrderDetail) *paypal.Capture {
if order == nil {
return nil
}
for _, unit := range order.PurchaseUnits {
if unit == nil || unit.Payments == nil {
continue
}
for _, capture := range unit.Payments.Captures {
if capture == nil || strings.TrimSpace(capture.Id) == "" {
continue
}
if strings.EqualFold(capture.Status, "COMPLETED") {
return capture
}
}
}
return nil
}
func paypalOrderCaptureState(order *paypal.OrderDetail) string {
if order == nil {
return "pending"
}
pending, failed, seen := false, false, false
for _, unit := range order.PurchaseUnits {
if unit == nil || unit.Payments == nil {
continue
}
for _, capture := range unit.Payments.Captures {
if capture == nil || strings.TrimSpace(capture.Id) == "" {
continue
}
seen = true
switch normalizePayPalCaptureState(capture.Status) {
case "success":
return "success"
case "pending":
pending = true
case "failed":
failed = true
}
}
}
if pending || !seen {
if strings.EqualFold(order.Status, "VOIDED") {
return "failed"
}
return "pending"
}
if failed {
return "failed"
}
return "pending"
}
func paypalOrderAmount(order *paypal.OrderDetail, c map[string]any) (int64, string, bool, error) {
var total int64
var currency string
found := false
for _, unit := range order.PurchaseUnits {
if unit == nil || unit.Amount == nil || strings.TrimSpace(unit.Amount.Value) == "" {
continue
}
unitCurrency := strings.ToUpper(strings.TrimSpace(unit.Amount.CurrencyCode))
amount, err := paypalParseAmount(unit.Amount, c)
if err != nil {
return 0, "", false, err
}
if found && currency != unitCurrency {
return 0, "", false, errors.New("PayPal 订单包含不同币种的 purchase unit")
}
if amount > math.MaxInt64-total {
return 0, "", false, errors.New("PayPal 订单金额超出范围")
}
currency = unitCurrency
total += amount
found = true
}
return total, currency, found, nil
}
func paypalRefundCaptureID(ctx context.Context, client *paypal.Client, req *biz.PaymentRefundRequest) (string, error) {
if req == nil {
return "", errors.New("PayPal 退款请求为空")
}
if captureID := strings.TrimSpace(req.ProviderTradeNo); captureID != "" {
return captureID, nil
}
orderID := strings.TrimSpace(req.QueryID)
if orderID == "" {
return "", errors.New("PayPal 退款缺少已持久化的 capture ID")
}
rsp, err := client.OrderDetail(ctx, orderID, nil)
if err != nil {
return "", err
}
if rsp == nil || rsp.Code != paypal.Success || rsp.Response == nil {
return "", paypalResponseError("退款前查单", paypalResponseCode(rsp), paypalOrderError(rsp))
}
if rsp.Response.Id != "" && rsp.Response.Id != orderID {
return "", errors.New("PayPal 退款查单响应的 order ID 不匹配")
}
returnedTradeNo, identityErr := paypalOrderIdentity(rsp.Response)
if identityErr != nil {
return "", identityErr
}
if returnedTradeNo == "" || returnedTradeNo != req.TradeNo {
return "", errors.New("PayPal 退款订单与本地商户订单号不匹配")
}
capture := paypalOrderCapture(rsp.Response)
if capture == nil || strings.TrimSpace(capture.Id) == "" {
return "", errors.New("PayPal 退款订单缺少 capture ID")
}
return capture.Id, nil
}
func paypalWebhookResult(event *paypal.WebhookEvent) (*biz.PaymentResult, error) {
var resource map[string]any
if err := json.Unmarshal(event.Resource, &resource); err != nil {
return nil, fmt.Errorf("PayPal 回调 resource 无效: %w", err)
}
eventType := strings.ToUpper(strings.TrimSpace(event.EventType))
tradeNo := firstNonEmpty(paypalMapString(resource, "invoice_id"), paypalMapString(resource, "custom_id"))
queryID := paypalNestedMapString(resource, "supplementary_data", "related_ids", "order_id")
providerTradeNo := ""
status := normalizePayPalCaptureState(paypalMapString(resource, "status"))
if strings.HasPrefix(eventType, "CHECKOUT.ORDER.") {
var order paypal.OrderDetail
if err := json.Unmarshal(event.Resource, &order); err != nil {
return nil, err
}
identity, identityErr := paypalOrderIdentity(&order)
if identityErr != nil {
return nil, identityErr
}
tradeNo = identity
queryID = order.Id
if capture := paypalOrderCapture(&order); capture != nil {
providerTradeNo = capture.Id
}
status = paypalOrderCaptureState(&order)
} else {
providerTradeNo = paypalMapString(resource, "id")
}
if tradeNo == "" {
tradeNo = paypalPurchaseUnitTradeNo(resource)
}
if tradeNo == "" || queryID == "" {
return nil, errors.New("PayPal 回调缺少商户订单号或 order ID")
}
return &biz.PaymentResult{
Provider: biz.PaymentPayPal,
Status: status,
TradeNo: tradeNo,
ProviderTradeNo: providerTradeNo,
QueryID: queryID,
}, nil
}
func paypalPurchaseUnitTradeNo(resource map[string]any) string {
units, _ := resource["purchase_units"].([]any)
for _, value := range units {
unit, _ := value.(map[string]any)
if tradeNo := firstNonEmpty(paypalMapString(unit, "invoice_id"), paypalMapString(unit, "custom_id"), paypalMapString(unit, "reference_id")); tradeNo != "" {
return tradeNo
}
}
return ""
}
func paypalCallbackHeader(callback *biz.PaymentCallback, name string) string {
for key, value := range callback.Headers {
if strings.EqualFold(key, name) {
return strings.TrimSpace(value)
}
}
return ""
}
func paypalFormatAmount(amount int64, currency string, c map[string]any) (string, error) {
scale, err := paypalAmountScale(currency, c)
if err != nil {
return "", err
}
return formatDecimalAmount(amount, scale)
}
func paypalParseAmount(amount *paypal.Amount, c map[string]any) (int64, error) {
if amount == nil {
return 0, errors.New("PayPal 金额为空")
}
scale, err := paypalAmountScale(amount.CurrencyCode, c)
if err != nil {
return 0, err
}
return parseDecimalAmount(amount.Value, scale)
}
func paypalAmountScale(currency string, c map[string]any) (int64, error) {
currency = strings.ToUpper(strings.TrimSpace(currency))
for _, key := range []string{"amount_scales", "currency_scales"} {
if scales, ok := c[key].(map[string]any); ok {
for configuredCurrency, raw := range scales {
if !strings.EqualFold(configuredCurrency, currency) {
continue
}
scale, err := strconv.ParseInt(strings.TrimSpace(fmt.Sprint(raw)), 10, 64)
if err != nil || scale <= 0 {
return 0, fmt.Errorf("PayPal %s 金额换算比例无效", currency)
}
if _, err = formatDecimalAmount(scale, scale); err != nil {
return 0, fmt.Errorf("PayPal %s 金额换算比例无效: %w", currency, err)
}
return scale, nil
}
}
}
if scale := configuredInt64(c, "amount_scale", 0); scale > 0 {
if _, err := formatDecimalAmount(scale, scale); err != nil {
return 0, fmt.Errorf("PayPal 金额换算比例无效: %w", err)
}
return scale, nil
}
switch currency {
case "HUF", "JPY", "TWD":
return 1, nil
default:
return 100, nil
}
}
func normalizePayPalOrderState(state string) string {
switch strings.ToUpper(strings.TrimSpace(state)) {
case "COMPLETED":
return "success"
case "VOIDED":
return "failed"
default:
return "pending"
}
}
func normalizePayPalCaptureState(state string) string {
switch strings.ToUpper(strings.TrimSpace(state)) {
case "COMPLETED", "PARTIALLY_REFUNDED", "REFUNDED":
return "success"
case "DECLINED", "FAILED", "DENIED", "VOIDED":
return "failed"
default:
return "pending"
}
}
func normalizePayPalRefundState(state string) string {
switch strings.ToUpper(strings.TrimSpace(state)) {
case "COMPLETED":
return "success"
case "CANCELLED", "FAILED":
return "failed"
default:
return "pending"
}
}
func paypalMapString(values map[string]any, key string) string {
value := values[key]
if value == nil {
return ""
}
return strings.TrimSpace(fmt.Sprint(value))
}
func paypalNestedMapString(values map[string]any, keys ...string) string {
var current any = values
for _, key := range keys {
object, ok := current.(map[string]any)
if !ok {
return ""
}
current = object[key]
}
if current == nil {
return ""
}
return strings.TrimSpace(fmt.Sprint(current))
}
func firstNonEmpty(values ...string) string {
for _, value := range values {
if value = strings.TrimSpace(value); value != "" && value != "<nil>" {
return value
}
}
return ""
}
func paypalResponseError(operation string, code int, detail string) error {
detail = strings.TrimSpace(detail)
if detail == "" {
detail = "未知错误"
}
if code == 0 {
return fmt.Errorf("PayPal %s失败: %s", operation, detail)
}
return fmt.Errorf("PayPal %s失败(%d): %s", operation, code, detail)
}
func paypalResponseCode(value any) int {
switch rsp := value.(type) {
case *paypal.CreateOrderRsp:
if rsp != nil {
return rsp.Code
}
case *paypal.OrderDetailRsp:
if rsp != nil {
return rsp.Code
}
case *paypal.PaymentCaptureRefundRsp:
if rsp != nil {
return rsp.Code
}
}
return 0
}
func paypalCreateError(rsp *paypal.CreateOrderRsp) string {
if rsp == nil {
return "响应为空"
}
return paypalErrorDetail(rsp.Error, rsp.ErrorResponse)
}
func paypalOrderError(rsp *paypal.OrderDetailRsp) string {
if rsp == nil {
return "响应为空"
}
return paypalErrorDetail(rsp.Error, rsp.ErrorResponse)
}
func paypalCaptureError(rsp *paypal.OrderCaptureRsp) string {
if rsp == nil {
return "响应为空"
}
return paypalErrorDetail(rsp.Error, rsp.ErrorResponse)
}
func paypalRefundError(rsp *paypal.PaymentCaptureRefundRsp) string {
if rsp == nil {
return "响应为空"
}
return paypalErrorDetail(rsp.Error, rsp.ErrorResponse)
}
func paypalErrorDetail(raw string, response *paypal.ErrorResponse) string {
if response != nil {
if value := firstNonEmpty(response.Message, response.Name); value != "" {
return value
}
}
return raw
}