使用Golang和gorilla-xmlrpc进行增值税校验

使用Golang和gorilla-xmlrpc进行增值税校验 我正在尝试以以下格式向一个XML-RPC服务器发送POST请求:

https://evatr.bff-online.de/evatrRPC?UstId_1=DE123456789&UstId_2=AB1234567890 &Firmenname=Firmenname einschl. Rechtsform&Ort=Ort der Firma&PLZ=12345&Strasse=Strasse der Firma &Druck=ja

读取结果并在Go语言中通过名称访问它们:

一个示例结果XML如下所示:

<params>
  <param>
    <value><array><data>
      <value><string>Datum</string></value>
      <value><string>27.07.2006</string></value>
    </data></array></value>
  </param>
  <param>
    <value><array><data>
      <value><string>Uhrzeit</string></value>
      <value><string>13:35:53</string></value>
    </data></array></value>
  </param>
  <param>
    <value><array><data>
      <value><string>ErrorCode</string></value>
      <value><string>200</string></value>
    </data></array></value>
  </param>
  <param>
    <value><array><data>
      <value><string>UstId_1</string></value>
      <value><string>DE123456789</string></value>
    </data></array></value>
  </param>
  <param>
    <value><array><data>
      <value><string>UstId_2</string></value>
      <value><string>AB1234567890</string></value>
    </data></array></value>
  </param>
  <param>
    <value><array><data>
      <value><string>Firmenname</string></value>
      <value><string>Firma XY Rechtsform</string></value>
    </data></array></value>
  </param>
  <param>
    <value><array><data>
      <value><string>Ort</string></value>
      <value><string>Firmenort</string></value>
    </data></array></value>
  </param>
  <param>
    <value><array><data>
      <value><string>PLZ</string></value>
      <value><string>1234</string></value>
    </data></array></value>
  </param>
  <param>
    <value><array><data>
      <value><string>Strasse</string></value>
      <value><string>Firmenstrasse</string></value>
    </data></array></value>
  </param>
  <param>
    <value><array><data>
      <value><string>Erg_Name</string></value>
      <value><string>A</string></value>
    </data></array></value>
  </param>
  <param>
    <value><array><data>
      <value><string>Erg_Ort</string></value>
      <value><string>A</string></value>
    </data></array></value>
  </param>
  <param>
    <value><array><data>
      <value><string>Erg_PLZ</string></value>
      <value><string>A</string></value>
    </data></array></value>
  </param>
  <param>
    <value><array><data>
      <value><string>Erg_Str</string></value>
      <value><string>A</string></value>
    </data></array></value>
  </param>
  <param>
    <value><array><data>
      <value><string>Gueltig_ab</string></value>
      <value><string></string></value>
    </data></array></value>
  </param>
  <param>
    <value><array><data>
      <value><string>Gueltig_bis</string></value>
      <value><string></string></value>
    </data></array></value>
  </param>
  <param>
    <value><array><data>
      <value><string>Druck</string></value>
      <value><string>ja</string></value>
    </data></array></value>
  </param>
</params>

我目前的进展如下:

type Params struct {
	XMLName xml2.Name   `xml:"params"`
	Text    string   `xml:",chardata"`
	Param   []struct {
		Text  string `xml:",chardata"`
		Value struct {
			Text  string `xml:",chardata"`
			Array struct {
				Text string `xml:",chardata"`
				Data struct {
					Text  string `xml:",chardata"`
					Value []struct {
						Text   string `xml:",chardata"`
						String string `xml:"string"`
					} `xml:"value"`
				} `xml:"data"`
			} `xml:"array"`
		} `xml:"value"`
	} `xml:"param"`
}

func XmlRpcCall(method string, args struct{UstId_1,UstId_2,Firmenname,Ort,PLZ,Strasse,Druck string}) (reply struct{Message string}, err error) {
	buf, _ := xml.EncodeClientRequest(method, &args)	
	resp, err := http.Post("https://evatr.bff-online.de/", "text/xml", bytes.NewBuffer(buf))
	if err != nil {
		return
	}
	defer resp.Body.Close()
	err = xml.DecodeClientResponse(resp.Body, &reply)
	return
}

func main() {

	reply, err := XmlRpcCall("evatrRPC", struct{UstId_1,UstId_2,Firmenname,Ort,PLZ,Strasse,Druck string}{"VAT1","VAT2","Name","Town","ZIP","Street","No"})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(reply.Message)
}

我的代码可以打印出XML消息,但我该如何将其解析到结构体中并访问例如错误代码值200呢?

我对Go语言还比较陌生,所以任何建议都会有帮助。


更多关于使用Golang和gorilla-xmlrpc进行增值税校验的实战教程也可以访问 https://www.itying.com/category-94-b0.html

9 回复

太棒了,非常感谢。等我完成这个函数后,我会把它贴出来,以防将来有人需要验证增值税号。

更多关于使用Golang和gorilla-xmlrpc进行增值税校验的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你好

你应该使用 encoding/xml 包中的 Unmarshal 函数 https://golang.org/pkg/encoding/xml/#Unmarshal

可以参考这个页面上的示例。你可以使用相同的技术,通过定义较小的部分来构建那个庞大的结构体,否则它会过于复杂且难以理解。

https://larry-price.com/blog/2015/12/04/xml-parsing-in-go/

你好

对于这个庞大的结构体,干得漂亮! 下面的代码行将打印 ErrorCode 和 200。我通过使用点号表示法来获取结构体的成员,并逐层深入到嵌套结构中。

		fmt.Println(container.Param[2].Value.Array.Data.Value[0].String)
		fmt.Println(container.Param[2].Value.Array.Data.Value[1].String)

如果你想遍历所有值,可以这样做:

	for _, p := range container.Param {
		for _, v := range p.Value.Array.Data.Value {
			fmt.Println(v.String)
		}
	}

使用 for range 遍历切片时,第一个返回值是索引,但我想要的是值,它通常是第二个返回值。这里我使用了占位符变量 _(下划线),因为我不关心索引,只关心值。

你甚至可以在XML标签中使用 > 指令来使结构体更加简洁

type Params struct {
	Param []struct {
		String []string `xml:"value>array>data>value>string"`	
	} `xml:"param"`
}

并像这样打印 ErrorCode 和 300

fmt.Println(container.Param[2].String[0])
fmt.Println(container.Param[2].String[1])

我是在阅读文档时发现这一点的。

文本字符串 xml:",chardata"

你需要这些吗?如果省略它们,结构体会小很多吗?

type Params struct {
	Param []struct {
		Value struct {
			Array struct {
				Data struct {
					Value []struct {
						String string `xml:"string"`
					} `xml:"value"`
				} `xml:"data"`
			} `xml:"array"`
		} `xml:"value"`
	} `xml:"param"`
}

就我所见,它运行得很好。

以下是用于检查欧洲增值税ID号的最终函数:

package main

import (
	"bytes"
	xml2 "encoding/xml"
	"fmt"
	"github.com/dannyvankooten/vat"
	"github.com/divan/gorilla-xmlrpc/xml"
)

func checkVat(UstId_1, UstId_2, Firmenname, Ort, PLZ, Strasse, Druck string) {

	validity, err := vat.ValidateNumberFormat(UstId_2)
	if err != nil {
		fmt.Println("Format der Umsatzsteuer ID unzulässig")
	}
	if validity == true {
		fmt.Println("Format der Umsatzsteuer ID zulässig")
	}
	validity2, err2 := vat.ValidateNumberExistence(UstId_2)
	if err2 != nil {
		fmt.Println("Umsatzsteuer ID ist unzulässig")
	}
	if validity2 == true {
		fmt.Println("Umsatzsteuer ID zulässig")
	}
	if validity && validity2 == true {
		XmlRpcCall("evatrRPC", struct{ UstId_1, UstId_2, Firmenname, Ort, PLZ, Strasse, Druck string }{UstId_1, UstId_2, Firmenname, Ort, PLZ, Strasse, Druck})
	}
}

func XmlRpcCall(method string, args struct{ UstId_1, UstId_2, Firmenname, Ort, PLZ, Strasse, Druck string }) {

	type Params struct {
		Param []struct {
			Value struct {
				Array struct {
					Data struct {
						Value []struct {
							String string `xml:"string"`
						} `xml:"value"`
					} `xml:"data"`
				} `xml:"array"`
			} `xml:"value"`
		} `xml:"param"`
	}

	type rep struct {
		Message string
	}

	var reply rep
	result := make(map[string]string)
	var Key string

	buf, _ := xml.EncodeClientRequest(method, &args)
	resp, err := http.Post("https://evatr.bff-online.de/", "text/xml", bytes.NewBuffer(buf))
	defer resp.Body.Close()
	err = xml.DecodeClientResponse(resp.Body, &reply)
	container := Params{}
	err = xml2.Unmarshal([]byte(reply.Message), &container)

	if err != nil {
		fmt.Println("Error:", err)
	}
	for _, p := range container.Param {

		for i, v := range p.Value.Array.Data.Value {
			if i == 0 {
				Key = v.String
			}
			if i == 1 {
				result[Key] = v.String
			}
		}
	}
	if result["ErrorCode"] == "200" {
		fmt.Println("Die angefragte USt-IdNr. ist gültig.")
	} else {
		fmt.Println("Die angefragte USt-IdNr. ist ungültig!")
	}
	fmt.Println(result)
	return

}

func main() {

	checkVat("YourVATId", "CustomerVATId", "Customer Company Name", "Customer City", "Customer Postcode", "Customer Street", "Print Yes/No")

}

checkVat 函数首先检查增值税ID号的格式是否正确,然后检查该号码是否已签发。如果两项检查都通过,它将根据客户地址请求验证该号码。"Print Yes/No"参数会触发德国税务部门发送一封包含您查询官方结果的信件。查询结果保存在一个名为result的映射中,其键对应于字段(UstId_1, UstId_2, Firmenname, Ort, PLZ, Strasse, Druck)。

希望其他人也能从中找到一些用处。如果对如何优化代码有任何建议,请告诉我。

我尝试按照那个例子,使用了这段代码,但我仍然无法单独访问这些值。例如,CodeError = 200 …

package main

import (
	"encoding/xml"
	"fmt"
)

var reply string

type Params struct {
	XMLName xml.Name `xml:"params"`
	Text    string   `xml:",chardata"`
	Param   []struct {
		Text  string `xml:",chardata"`
		Value struct {
			Text  string `xml:",chardata"`
			Array struct {
				Text string `xml:",chardata"`
				Data struct {
					Text  string `xml:",chardata"`
					Value []struct {
						Text   string `xml:",chardata"`
						String string `xml:"string"`
					} `xml:"value"`
				} `xml:"data"`
			} `xml:"array"`
		} `xml:"value"`
	} `xml:"param"`
}

func main() {
	reply = `
<params>
<param>
<value><array><data>
<value><string>Datum</string></value>
<value><string>27.07.2006</string></value>
</data></array></value>
</param>
<param>
<value><array><data>
<value><string>Uhrzeit</string></value>
<value><string>13:35:53</string></value>
</data></array></value>
</param>
<param>
<value><array><data>
<value><string>ErrorCode</string></value>
<value><string>200</string></value>
</data></array></value>
</param>
<param>
<value><array><data>
<value><string>UstId_1</string></value>
<value><string>DE123456789</string></value>
</data></array></value>
</param>
<param>
<value><array><data>
<value><string>UstId_2</string></value>
<value><string>AB1234567890</string></value>
</data></array></value>
</param>
<param>
<value><array><data>
<value><string>Firmenname</string></value>
<value><string>Firma XY Rechtsform</string></value>
</data></array></value>
</param>
<param>
<value><array><data>
<value><string>Ort</string></value>
<value><string>Firmenort</string></value>
</data></array></value>
</param>
<param>
<value><array><data>
<value><string>PLZ</string></value>
<value><string>1234</string></value>
</data></array></value>
</param>
<param>
<value><array><data>
<value><string>Strasse</string></value>
<value><string>Firmenstrasse</string></value>
</data></array></value>
</param>
<param>
<value><array><data>
<value><string>Erg_Name</string></value>
<value><string>A</string></value>
</data></array></value>
</param>
<param>
<value><array><data>
<value><string>Erg_Ort</string></value>
<value><string>A</string></value>
</data></array></value>
</param>
<param>
<value><array><data>
<value><string>Erg_PLZ</string></value>
<value><string>A</string></value>
</data></array></value>
</param>
<param>
<value><array><data>
<value><string>Erg_Str</string></value>
<value><string>A</string></value>
</data></array></value>
</param>
<param>
<value><array><data>
<value><string>Gueltig_ab</string></value>
<value><string></string></value>
</data></array></value>
</param>
<param>
<value><array><data>
<value><string>Gueltig_bis</string></value>
<value><string></string></value>
</data></array></value>
</param>
<param>
<value><array><data>
<value><string>Druck</string></value>
<value><string>ja</string></value>
</data></array></value>
</param>
</params>
`

	container := Params{}
	err := xml.Unmarshal([]byte(reply), &container)

	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(container)
	}

}

结果:

{{ params}

[{

{{{

[{ Datum} { 27.07.2006}]}}}} {{

{{{

[{ Uhrzeit} { 13:35:53}]}}}} {{

{{{

[{ ErrorCode} { 200}]}}}} {{

{{{

[{ UstId_1} { DE123456789}]}}}} {{

{{{

[{ UstId_2} { AB1234567890}]}}}} {{

{{{

[{ Firmenname} { Firma XY Rechtsform}]}}}} {{

{{{

[{ Ort} { Firmenort}]}}}} {{

{{{

[{ PLZ} { 1234}]}}}} {{

{{{

[{ Strasse} { Firmenstrasse}]}}}} {{

{{{

[{ Erg_Name} { A}]}}}} {{

{{{

[{ Erg_Ort} { A}]}}}} {{

{{{

[{ Erg_PLZ} { A}]}}}} {{

{{{

[{ Erg_Str} { A}]}}}} {{

{{{

[{ Gueltig_ab} { }]}}}} {{

{{{

[{ Gueltig_bis} { }]}}}} {{

{{{

[{ Druck} { ja}]}}}}]}

使用gorilla-xmlrpc处理增值税校验的响应,需要正确解析XML-RPC的嵌套数据结构。以下是完整的解决方案:

package main

import (
    "bytes"
    "encoding/xml"
    "fmt"
    "io"
    "net/http"
    "github.com/gorilla/xmlrpc"
)

// 定义响应数据结构
type VATResponse struct {
    Datum      string `xmlrpc:"Datum"`
    Uhrzeit    string `xmlrpc:"Uhrzeit"`
    ErrorCode  string `xmlrpc:"ErrorCode"`
    UstId_1    string `xmlrpc:"UstId_1"`
    UstId_2    string `xmlrpc:"UstId_2"`
    Firmenname string `xmlrpc:"Firmenname"`
    Ort        string `xmlrpc:"Ort"`
    PLZ        string `xmlrpc:"PLZ"`
    Strasse    string `xmlrpc:"Strasse"`
    Erg_Name   string `xmlrpc:"Erg_Name"`
    Erg_Ort    string `xmlrpc:"Erg_Ort"`
    Erg_PLZ    string `xmlrpc:"Erg_PLZ"`
    Erg_Str    string `xmlrpc:"Erg_Str"`
    Gueltig_ab string `xmlrpc:"Gueltig_ab"`
    Gueltig_bis string `xmlrpc:"Gueltig_bis"`
    Druck      string `xmlrpc:"Druck"`
}

// 自定义XML-RPC解码器处理嵌套数组结构
func decodeVATResponse(data []byte) (*VATResponse, error) {
    type param struct {
        Value []string `xml:"array>data>value>string"`
    }
    
    type response struct {
        XMLName xml.Name `xml:"params"`
        Params  []param  `xml:"param"`
    }
    
    var resp response
    if err := xml.Unmarshal(data, &resp); err != nil {
        return nil, err
    }
    
    result := &VATResponse{}
    for _, p := range resp.Params {
        if len(p.Value) == 2 {
            key := p.Value[0]
            value := p.Value[1]
            
            switch key {
            case "Datum":
                result.Datum = value
            case "Uhrzeit":
                result.Uhrzeit = value
            case "ErrorCode":
                result.ErrorCode = value
            case "UstId_1":
                result.UstId_1 = value
            case "UstId_2":
                result.UstId_2 = value
            case "Firmenname":
                result.Firmenname = value
            case "Ort":
                result.Ort = value
            case "PLZ":
                result.PLZ = value
            case "Strasse":
                result.Strasse = value
            case "Erg_Name":
                result.Erg_Name = value
            case "Erg_Ort":
                result.Erg_Ort = value
            case "Erg_PLZ":
                result.Erg_PLZ = value
            case "Erg_Str":
                result.Erg_Str = value
            case "Gueltig_ab":
                result.Gueltig_ab = value
            case "Gueltig_bis":
                result.Gueltig_bis = value
            case "Druck":
                result.Druck = value
            }
        }
    }
    
    return result, nil
}

func XmlRpcCall(method string, args map[string]string) (*VATResponse, error) {
    // 使用gorilla-xmlrpc编码请求
    buf, err := xmlrpc.EncodeClientRequest(method, args)
    if err != nil {
        return nil, err
    }
    
    // 发送请求
    resp, err := http.Post("https://evatr.bff-online.de/evatrRPC", "text/xml", bytes.NewBuffer(buf))
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    // 读取响应
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }
    
    // 解码响应
    return decodeVATResponse(body)
}

func main() {
    args := map[string]string{
        "UstId_1":   "DE123456789",
        "UstId_2":   "AB1234567890",
        "Firmenname": "Firmenname einschl. Rechtsform",
        "Ort":       "Ort der Firma",
        "PLZ":       "12345",
        "Strasse":   "Strasse der Firma",
        "Druck":     "ja",
    }
    
    response, err := XmlRpcCall("evatrRPC", args)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    
    // 访问具体字段
    fmt.Printf("ErrorCode: %s\n", response.ErrorCode)
    fmt.Printf("Datum: %s\n", response.Datum)
    fmt.Printf("Uhrzeit: %s\n", response.Uhrzeit)
    fmt.Printf("Firmenname: %s\n", response.Firmenname)
    fmt.Printf("Erg_Name: %s\n", response.Erg_Name)
}

如果需要使用标准库的xml包而不依赖gorilla-xmlrpc,可以使用以下替代方案:

package main

import (
    "bytes"
    "encoding/xml"
    "fmt"
    "io"
    "net/http"
    "strings"
)

// 使用xml包直接解析
func parseVATResponseXML(xmlData string) (map[string]string, error) {
    type StringValue struct {
        String string `xml:"string"`
    }
    
    type Data struct {
        Values []StringValue `xml:"value"`
    }
    
    type Array struct {
        Data Data `xml:"data"`
    }
    
    type Value struct {
        Array Array `xml:"array"`
    }
    
    type Param struct {
        Value Value `xml:"value"`
    }
    
    type Params struct {
        Params []Param `xml:"param"`
    }
    
    var result Params
    err := xml.Unmarshal([]byte(xmlData), &result)
    if err != nil {
        return nil, err
    }
    
    dataMap := make(map[string]string)
    for _, param := range result.Params {
        if len(param.Value.Array.Data.Values) == 2 {
            key := param.Value.Array.Data.Values[0].String
            value := param.Value.Array.Data.Values[1].String
            dataMap[key] = value
        }
    }
    
    return dataMap, nil
}

func makeXMLRPCRequest(method string, params map[string]string) (string, error) {
    var builder strings.Builder
    
    builder.WriteString(`<?xml version="1.0"?>`)
    builder.WriteString(`<methodCall>`)
    builder.WriteString(`<methodName>` + method + `</methodName>`)
    builder.WriteString(`<params>`)
    
    for key, value := range params {
        builder.WriteString(`<param><value><struct>`)
        builder.WriteString(`<member>`)
        builder.WriteString(`<name>` + key + `</name>`)
        builder.WriteString(`<value><string>` + value + `</string></value>`)
        builder.WriteString(`</member>`)
        builder.WriteString(`</struct></value></param>`)
    }
    
    builder.WriteString(`</params>`)
    builder.WriteString(`</methodCall>`)
    
    return builder.String(), nil
}

func main() {
    params := map[string]string{
        "UstId_1":   "DE123456789",
        "UstId_2":   "AB1234567890",
        "Firmenname": "Firmenname einschl. Rechtsform",
        "Ort":       "Ort der Firma",
        "PLZ":       "12345",
        "Strasse":   "Strasse der Firma",
        "Druck":     "ja",
    }
    
    xmlRequest, err := makeXMLRPCRequest("evatrRPC", params)
    if err != nil {
        fmt.Printf("Error creating request: %v\n", err)
        return
    }
    
    resp, err := http.Post("https://evatr.bff-online.de/evatrRPC", "text/xml", 
        bytes.NewBufferString(xmlRequest))
    if err != nil {
        fmt.Printf("Error making request: %v\n", err)
        return
    }
    defer resp.Body.Close()
    
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Printf("Error reading response: %v\n", err)
        return
    }
    
    result, err := parseVATResponseXML(string(body))
    if err != nil {
        fmt.Printf("Error parsing response: %v\n", err)
        return
    }
    
    // 访问具体字段
    fmt.Printf("ErrorCode: %s\n", result["ErrorCode"])
    fmt.Printf("Datum: %s\n", result["Datum"])
    fmt.Printf("Firmenname: %s\n", result["Firmenname"])
    fmt.Printf("Erg_Name: %s\n", result["Erg_Name"])
}

这两种方法都能正确解析XML-RPC响应并让你通过字段名访问数据,特别是ErrorCode字段。第一种方法使用gorilla-xmlrpc处理编码,第二种方法使用标准库手动构建请求。

回到顶部