package paymentutil import ( "encoding/json" "errors" "fmt" ) const ( ProviderAlipay = "alipay" ProviderAlipayV3 = "alipay-v3" ProviderWechatV2 = "wechat-v2" ProviderWechatV3 = "wechat-v3" ProviderApple = "apple-iap" ) // CallbackAck is the provider-facing HTTP acknowledgement returned after a // payment notification is processed. type CallbackAck struct { StatusCode int ContentType string Body []byte } // CallbackError carries a safe provider acknowledgement alongside the cause // that should be logged or returned to the application layer. type CallbackError struct { Cause error Ack CallbackAck } func (e *CallbackError) Error() string { if e == nil || e.Cause == nil { return "支付回调处理失败" } return e.Cause.Error() } func (e *CallbackError) Unwrap() error { if e == nil { return nil } return e.Cause } func CallbackFailure(err error, fallback CallbackAck) CallbackAck { var callbackErr *CallbackError if errors.As(err, &callbackErr) && callbackErr.Ack.StatusCode != 0 { return callbackErr.Ack } return fallback } // DefaultCallbackAck returns the conventional acknowledgement for providers // whose callback wire formats are stable across modules. func DefaultCallbackAck(provider string, success bool) CallbackAck { status := 200 if !success { status = 500 } switch provider { case ProviderAlipay, ProviderAlipayV3: body := "success" if !success { body = "failure" } return CallbackAck{StatusCode: status, ContentType: "text/plain; charset=utf-8", Body: []byte(body)} case ProviderWechatV2: code, message := "SUCCESS", "OK" if !success { code, message = "FAIL", "FAIL" } body := fmt.Sprintf("", code, message) return CallbackAck{StatusCode: status, ContentType: "application/xml; charset=utf-8", Body: []byte(body)} case ProviderWechatV3: code, message := "SUCCESS", "成功" if !success { code, message = "FAIL", "失败" } body, _ := json.Marshal(map[string]string{"code": code, "message": message}) return CallbackAck{StatusCode: status, ContentType: "application/json; charset=utf-8", Body: body} case ProviderApple: return CallbackAck{StatusCode: status} default: body := "success" if !success { body = "failure" } return CallbackAck{StatusCode: status, ContentType: "text/plain; charset=utf-8", Body: []byte(body)} } }