-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
401 lines (359 loc) · 12.8 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
package main
import (
"encoding/base64"
"encoding/json"
"encoding/xml"
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"strings"
"time"
_ "embed"
"github.com/google/uuid"
"github.com/joho/godotenv"
"github.com/manifoldco/promptui"
)
type Object struct {
Invoice invoice `json:"invoice"`
}
type invoice struct {
CorrelationID string `json:"@_documentCorrelationId" xml:"documentCorrelationId,attr"`
DocTypeID string `json:"@_docTypeId" xml:"docTypeId,attr"`
DocInstanceID int64 `json:"@_docInstanceId" xml:"docInstanceId,attr"`
DocPlatform string `json:"@_docPlatform" xml:"docPlatform,attr"`
Serie string `json:"@_serie" xml:"serie,attr"`
CurrencyISO string `json:"currencyISOCode" xml:"currencyISOCode"`
DocumentRectificationPurpose string `json:"documentRectificationPurpose,omitempty" xml:"documentRectificationPurpose,omitempty"`
References struct {
ThirdPartyErpInternalReference string `json:"thirdPartyErpInternalReference" xml:"thirdPartyErpInternalReference"`
InvoiceReference int64 `json:"invoiceReference,omitempty" xml:"invoiceReference,omitempty"`
} `json:"documentReferences" xml:"documentReferences"`
Dates struct {
DocumentDate string `json:"documentDate" xml:"documentDate"`
GoodsServiceAvailableDate string `json:"goodsServiceAvailableDate" xml:"goodsServiceAvailableDate"`
DueDate string `json:"dueDate" xml:"dueDate"`
} `json:"documentDates" xml:"documentDates"`
Parties struct {
Seller struct {
Name string `json:"name" xml:"name"`
Country string `json:"country" xml:"country"`
VATNumber string `json:"vatNumber" xml:"vatNumber"`
Address string `json:"address" xml:"address"`
City string `json:"city" xml:"city"`
ZipArea string `json:"zipArea" xml:"zipArea"`
ZipCode string `json:"zipCode" xml:"zipCode"`
CompanyRegistrationNumber string `json:"companyRegistrationNumber" xml:"companyRegistrationNumber"`
CompanyRegistrationLocation string `json:"companyRegistrationLocation" xml:"companyRegistrationLocation"`
SocialCapitalValue string `json:"socialCapitalValue" xml:"socialCapitalValue"`
} `json:"seller" xml:"seller"`
Buyer struct {
Name string `json:"name" xml:"name"`
Email string `json:"email" xml:"email"`
Country string `json:"country" xml:"country"`
VATNumber string `json:"vatNumber" xml:"vatNumber"`
Address string `json:"address" xml:"address"`
ZipArea string `json:"zipArea" xml:"zipArea"`
ZipCode string `json:"zipCode" xml:"zipCode"`
} `json:"buyer" xml:"buyer"`
} `json:"partyInformation" xml:"partyInformation"`
LineItems []struct {
Number int64 `json:"@_number" xml:"number,attr"`
SellerAssignedTradeItemID string `json:"sellerAssignedTradeItemIdentification" xml:"sellerAssignedTradeItemIdentification"`
ItemDescription string `json:"itemDescription" xml:"itemDescription"`
NetPrice float64 `json:"netPrice" xml:"netPrice"`
NetLineAmount float64 `json:"netLineAmount" xml:"netLineAmount"`
GrossPrice float64 `json:"grossPrice" xml:"grossPrice"`
GrossLineAmount float64 `json:"grossLineAmount" xml:"grossLineAmount"`
LineTotalPayableAmount float64 `json:"lineTotalPayableAmount" xml:"lineTotalPayableAmount"`
LineTotalDiscountAmount float64 `json:"lineTotalDiscountAmount" xml:"lineTotalDiscountAmount"`
LineDiscount *struct {
DiscountPercentage float64 `json:"discountPercentage" xml:"discountPercentage"`
DiscountAmount float64 `json:"discountAmount" xml:"discountAmount"`
} `json:"lineDiscount,omitempty" xml:"lineDiscount,omitempty"`
Quantity struct {
Value int `json:"value" xml:"value"`
UnitCodeValue string `json:"unitCodeValue" xml:"unitCodeValue"`
} `json:"quantity" xml:"quantity"`
LineVat struct {
TaxableAmount float64 `json:"taxableAmount" xml:"taxableAmount"`
TaxPercentage float64 `json:"taxPercentage" xml:"taxPercentage"`
TaxTotalValue float64 `json:"taxTotalValue" xml:"taxTotalValue"`
} `json:"lineVat" xml:"lineVat"`
} `json:"lineItem" xml:"lineItem"`
Totals struct {
NumberOfLines int64 `json:"numberOfLines" xml:"numberOfLines"`
TotalAmountPayable float64 `json:"totalAmountPayable" xml:"totalAmountPayable"`
TotalVatTaxableAmount float64 `json:"totalVatTaxableAmount" xml:"totalVatTaxableAmount"`
TotalVatAmount float64 `json:"totalVatAmount" xml:"totalVatAmount"`
TotalGrossAmount float64 `json:"totalGrossAmount" xml:"totalGrossAmount"`
TotalNetAmount float64 `json:"totalNetAmount" xml:"totalNetAmount"`
TotalDiscountAmount float64 `json:"totalDiscountAmount,omitempty" xml:"totalDiscountAmount,omitempty"`
VatSummary struct {
TaxPercentage float64 `json:"taxPercentage" xml:"taxPercentage"`
TaxTotalValue float64 `json:"taxTotalValue" xml:"taxTotalValue"`
TaxableAmount float64 `json:"taxableAmount" xml:"taxableAmount"`
} `json:"vatSummary" xml:"vatSummary"`
} `json:"documentTotals" xml:"documentTotals"`
EmailNotification struct {
Email string `json:"email" xml:"email"`
LanguageCode string `json:"languageCode" xml:"languageCode"`
} `json:"emailNotification" xml:"emailNotification"`
}
type MessageResponseObject struct {
Result MessageBodyObject `json:"ResultData"`
}
type MessagesResponseObject struct {
Results MessageHeaderObject `json:"ResultData"`
}
type MessageHeaderObject struct {
Messages []MessageBodyObject `json:"MessageIds"`
}
type MessageBodyObject struct {
ID string `json:"MessageId"`
Receiver string `json:"Receiver"`
Sender string `json:"Sender"`
Base64Data string `json:"Base64Data,omitempty"`
}
var (
//go:embed payload.json
_payload []byte
)
func main() {
var sendPayload bool
var jsonString string
var getMessages bool
var clearMessages bool
var getMessageId string
var processMessageId string
var monitorQueue bool
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
flag.BoolVar(&sendPayload, "send", false, "Send a payload to the queue")
flag.StringVar(&jsonString, "payload", "", "JSON string of a payload to send to the queue")
flag.BoolVar(&getMessages, "getmessages", false, "Get all message IDs")
flag.BoolVar(&clearMessages, "clearmessages", false, "Clear all messages in the queue")
flag.StringVar(&getMessageId, "getmessage", "", "Get message by ID")
flag.StringVar(&processMessageId, "processmessage", "", "Process message by ID")
flag.BoolVar(&monitorQueue, "monitor", false, "Monitor the queue for any new messages")
flag.Parse()
if sendPayload {
if jsonString == "" {
jsonString = string(_payload)
}
sendMessage(jsonString)
} else if getMessages {
messages := getAllMessages()
messageJSON, err := json.MarshalIndent(messages, "", " ")
if err != nil {
log.Fatalf(err.Error())
}
fmt.Println(string(messageJSON))
} else if getMessageId != "" {
decode := yesNo("Do you want to return just the Base64 decoded response?")
message, decodedResult := getMessageById(getMessageId, decode)
if decode {
fmt.Println(decodedResult)
} else {
messageJSON, err := json.MarshalIndent(message, "", " ")
if err != nil {
log.Fatalf(err.Error())
}
fmt.Println(string(messageJSON))
}
} else if processMessageId != "" {
if !processMessageById(processMessageId) {
fmt.Printf("Failed to clear message %s from the queue\n", processMessageId)
} else {
fmt.Printf("Message %s cleared from the queue\n", processMessageId)
}
} else if clearMessages {
clearAllMessages()
} else if monitorQueue {
monitor()
} else {
fmt.Println("Invalid arguments")
flag.Usage()
}
}
func decodePayloadReseponse(base64String string) string {
decoded, err := base64.StdEncoding.DecodeString(base64String)
if err != nil {
panic(err)
}
return string(decoded)
}
func sendMessage(jsonString string) {
var xmlData []byte
var err error
var fileName string
payload := Object{}
if err := json.Unmarshal([]byte(jsonString), &payload); err != nil {
panic(err)
}
fileName = payload.Invoice.References.ThirdPartyErpInternalReference
xmlData, err = xml.MarshalIndent(payload.Invoice, "", " ")
if err != nil {
panic(err)
}
xmlPayload := fmt.Sprintf("%s%s", "", xmlData)
url := fmt.Sprintf(
"%s%s",
os.Getenv("SOVOS_BASE_URL"),
os.Getenv("SOVOS_RECEIVE_ENDPOINT"),
)
method := "POST"
id := uuid.New()
postBody := strings.NewReader(fmt.Sprintf(
`{
"Sender": "%s",
"Receiver": "%s",
"ContentType": "application/xml",
"Base64Data": "%s",
"MessageId": "%s",
"Filename": "%v.xml"
}`,
os.Getenv("SOVOS_USER_ID"),
os.Getenv("SOVOS_ENVIRONMENT"),
base64.StdEncoding.EncodeToString([]byte(xmlPayload)),
id.String(),
fileName,
))
response, err := makeRequest(method, url, postBody)
if err != nil {
log.Fatalf("Error with PROCESSQUEUEDMESSAGE call: %s", err)
}
fmt.Println(xmlPayload)
fmt.Println(postBody)
fmt.Println(string(response))
}
func getAllMessages() MessagesResponseObject {
url := fmt.Sprintf(
"%s%s?Receiver=%s",
os.Getenv("SOVOS_BASE_URL"),
os.Getenv("SOVOS_GET_MESSAGES_ENDPOINT"),
os.Getenv("SOVOS_USER_ID"),
)
method := "GET"
body, err := makeRequest(method, url, nil)
if err != nil {
log.Fatalf("Error with GETQUEUEDMESSAGES call: %s", err)
}
messages := MessagesResponseObject{}
if err := json.Unmarshal(body, &messages); err != nil {
panic(err)
}
return messages
}
func getMessageById(messageID string, decode bool) (MessageBodyObject, string) {
for _, result := range getAllMessages().Results.Messages {
if messageID == result.ID {
url := fmt.Sprintf(
"%s%s?Receiver=%s&Sender=%s&MessageID=%s",
os.Getenv("SOVOS_BASE_URL"),
os.Getenv("SOVOS_GET_MESSAGE_ENDPOINT"),
result.Receiver,
result.Sender,
messageID,
)
method := "GET"
body, err := makeRequest(method, url, nil)
if err != nil {
log.Fatalf("Error with GETMESSAGEDATA call: %s", err)
}
message := MessageResponseObject{}
if err := json.Unmarshal(body, &message); err != nil {
panic(err)
}
if decode {
return MessageBodyObject{}, decodePayloadReseponse(message.Result.Base64Data)
}
return message.Result, ""
}
}
return MessageBodyObject{}, ""
}
func processMessageById(messageID string) bool {
for _, result := range getAllMessages().Results.Messages {
if messageID == result.ID {
url := fmt.Sprintf(
"%s%s",
os.Getenv("SOVOS_BASE_URL"),
os.Getenv("SOVOS_PROCESS_MESSAGE_ENDPOINT"),
)
method := "POST"
payload := fmt.Sprintf(`{
"Receiver": "%s",
"Sender": "%s",
"MessageId": "%s"
}`, result.Receiver, result.Sender, messageID)
payloadReader := strings.NewReader(payload)
_, err := makeRequest(method, url, payloadReader)
if err != nil {
log.Fatalf("Error with PROCESSQUEUEDMESSAGE call: %s", err)
return false
}
return true
}
}
return false
}
func getAuthHeader() string {
return "Basic " + base64.StdEncoding.EncodeToString([]byte(os.Getenv("SOVOS_API_KEY")+":"+os.Getenv("SOVOS_API_SECRET")))
}
func makeRequest(method string, url string, payload io.Reader) ([]byte, error) {
client := &http.Client{}
req, err := http.NewRequest(method, url, payload)
if err != nil {
return []byte(""), err
}
req.Header.Add("Authorization", getAuthHeader())
req.Header.Add("Content-Type", "application/json")
res, err := client.Do(req)
if err != nil {
return []byte(""), err
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
return body, err
}
func clearAllMessages() {
if !yesNo(fmt.Sprintf("Are you sure? This will clear ALL messages currently on the %s queue", os.Getenv("SOVOS_ENVIRONMENT"))) {
os.Exit(1)
}
for _, message := range getAllMessages().Results.Messages {
if !processMessageById(message.ID) {
fmt.Printf("Failed to clear message %s from the queue\n", message.ID)
} else {
fmt.Printf("Message %s cleared from the queue\n", message.ID)
}
}
}
func monitor() {
currentMessageCount := len(getAllMessages().Results.Messages)
fmt.Printf("Monitoring queue - currently %d message/s in the queue\n", currentMessageCount)
for {
time.Sleep(5 * time.Second)
newMessageCount := len(getAllMessages().Results.Messages)
if newMessageCount != currentMessageCount {
fmt.Printf("%d new message/s found on the queue\n", newMessageCount-currentMessageCount)
currentMessageCount = newMessageCount
fmt.Printf("Monitoring queue - currently %d message/s in the queue\n", currentMessageCount)
}
}
}
func yesNo(label string) bool {
prompt := promptui.Select{
Label: label,
Items: []string{"Yes", "No"},
}
_, result, err := prompt.Run()
if err != nil {
log.Fatalf("Prompt failed %v\n", err)
}
return result == "Yes"
}