+30
.gitignore
+30
.gitignore
···
1
+
# Created by https://www.toptal.com/developers/gitignore/api/go
2
+
# Edit at https://www.toptal.com/developers/gitignore?templates=go
3
+
4
+
### Go ###
5
+
# If you prefer the allow list template instead of the deny list, see community template:
6
+
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
7
+
#
8
+
# Binaries for programs and plugins
9
+
*.exe
10
+
*.exe~
11
+
*.dll
12
+
*.so
13
+
*.dylib
14
+
bin/
15
+
16
+
# Test binary, built with `go test -c`
17
+
*.test
18
+
19
+
# Output of the go coverage tool, specifically when used with LiteIDE
20
+
*.out
21
+
22
+
# Dependency directories (remove the comment below to include it)
23
+
# vendor/
24
+
25
+
# Go workspace file
26
+
go.work
27
+
28
+
# End of https://www.toptal.com/developers/gitignore/api/go
29
+
30
+
*.hurl
+27
cmd/app/main.go
+27
cmd/app/main.go
···
1
+
package main
2
+
3
+
import (
4
+
"log"
5
+
"os"
6
+
7
+
"github.com/Tulkdan/payment-gateway/internal/service"
8
+
"github.com/Tulkdan/payment-gateway/internal/web"
9
+
)
10
+
11
+
func getEnv(key, defaultValue string) string {
12
+
if value := os.Getenv(key); value != "" {
13
+
return value
14
+
}
15
+
return defaultValue
16
+
}
17
+
18
+
func main() {
19
+
paymentsService := service.NewPaymentService()
20
+
21
+
server := web.NewServer(paymentsService, "8000")
22
+
server.ConfigureRouter()
23
+
24
+
if err := server.Start(); err != nil {
25
+
log.Fatal("Error starting server: ", err)
26
+
}
27
+
}
+5
go.mod
+5
go.mod
+2
go.sum
+2
go.sum
+371
internal/constants/iso4217.go
+371
internal/constants/iso4217.go
···
1
+
package constants
2
+
3
+
var lookupTable = map[string]struct{}{
4
+
"AFN": {}, // Afghani
5
+
"971": {}, // Afghani
6
+
"EUR": {}, // Euro
7
+
"978": {}, // Euro
8
+
"ALL": {}, // Lek
9
+
"008": {}, // Lek
10
+
"DZD": {}, // Algerian Dinar
11
+
"012": {}, // Algerian Dinar
12
+
"USD": {}, // US Dollar
13
+
"840": {}, // US Dollar
14
+
"AOA": {}, // Kwanza
15
+
"973": {}, // Kwanza
16
+
"XCD": {}, // East Caribbean Dollar
17
+
"951": {}, // East Caribbean Dollar
18
+
"ARS": {}, // Argentine Peso
19
+
"032": {}, // Argentine Peso
20
+
"AMD": {}, // Armenian Dram
21
+
"051": {}, // Armenian Dram
22
+
"AWG": {}, // Aruban Florin
23
+
"533": {}, // Aruban Florin
24
+
"AUD": {}, // Australian Dollar
25
+
"036": {}, // Australian Dollar
26
+
"AZN": {}, // Azerbaijan Manat
27
+
"944": {}, // Azerbaijan Manat
28
+
"BSD": {}, // Bahamian Dollar
29
+
"044": {}, // Bahamian Dollar
30
+
"BHD": {}, // Bahraini Dinar
31
+
"048": {}, // Bahraini Dinar
32
+
"BDT": {}, // Taka
33
+
"050": {}, // Taka
34
+
"BBD": {}, // Barbados Dollar
35
+
"052": {}, // Barbados Dollar
36
+
"BYN": {}, // Belarusian Ruble
37
+
"933": {}, // Belarusian Ruble
38
+
"BZD": {}, // Belize Dollar
39
+
"084": {}, // Belize Dollar
40
+
"XOF": {}, // CFA Franc BCEAO
41
+
"952": {}, // CFA Franc BCEAO
42
+
"BMD": {}, // Bermudian Dollar
43
+
"060": {}, // Bermudian Dollar
44
+
"INR": {}, // Indian Rupee
45
+
"356": {}, // Indian Rupee
46
+
"BTN": {}, // Ngultrum
47
+
"064": {}, // Ngultrum
48
+
"BOB": {}, // Boliviano
49
+
"068": {}, // Boliviano
50
+
"BOV": {}, // Mvdol
51
+
"984": {}, // Mvdol
52
+
"BAM": {}, // Convertible Mark
53
+
"977": {}, // Convertible Mark
54
+
"BWP": {}, // Pula
55
+
"072": {}, // Pula
56
+
"NOK": {}, // Norwegian Krone
57
+
"578": {}, // Norwegian Krone
58
+
"BRL": {}, // Brazilian Real
59
+
"986": {}, // Brazilian Real
60
+
"BND": {}, // Brunei Dollar
61
+
"096": {}, // Brunei Dollar
62
+
"BGN": {}, // Bulgarian Lev
63
+
"975": {}, // Bulgarian Lev
64
+
"BIF": {}, // Burundi Franc
65
+
"108": {}, // Burundi Franc
66
+
"CVE": {}, // Cabo Verde Escudo
67
+
"132": {}, // Cabo Verde Escudo
68
+
"KHR": {}, // Riel
69
+
"116": {}, // Riel
70
+
"XAF": {}, // CFA Franc BEAC
71
+
"950": {}, // CFA Franc BEAC
72
+
"CAD": {}, // Canadian Dollar
73
+
"124": {}, // Canadian Dollar
74
+
"KYD": {}, // Cayman Islands Dollar
75
+
"136": {}, // Cayman Islands Dollar
76
+
"CLP": {}, // Chilean Peso
77
+
"152": {}, // Chilean Peso
78
+
"CLF": {}, // Unidad de Fomento
79
+
"990": {}, // Unidad de Fomento
80
+
"CNY": {}, // Yuan Renminbi
81
+
"156": {}, // Yuan Renminbi
82
+
"CNH": {}, // Yuan Fen
83
+
"157": {}, // Yuan Fen
84
+
"COP": {}, // Colombian Peso
85
+
"170": {}, // Colombian Peso
86
+
"COU": {}, // Unidad de Valor Real
87
+
"970": {}, // Unidad de Valor Real
88
+
"KMF": {}, // Comorian Franc
89
+
"174": {}, // Comorian Franc
90
+
"CDF": {}, // Congolese Franc
91
+
"976": {}, // Congolese Franc
92
+
"NZD": {}, // New Zealand Dollar
93
+
"554": {}, // New Zealand Dollar
94
+
"CRC": {}, // Costa Rican Colon
95
+
"188": {}, // Costa Rican Colon
96
+
"HRK": {}, // Kuna
97
+
"191": {}, // Kuna
98
+
"CUP": {}, // Cuban Peso
99
+
"192": {}, // Cuban Peso
100
+
"CUC": {}, // Peso Convertible
101
+
"931": {}, // Peso Convertible
102
+
"ANG": {}, // Netherlands Antillean Guilder
103
+
"532": {}, // Netherlands Antillean Guilder
104
+
"CZK": {}, // Czech Koruna
105
+
"203": {}, // Czech Koruna
106
+
"DKK": {}, // Danish Krone
107
+
"208": {}, // Danish Krone
108
+
"DJF": {}, // Djibouti Franc
109
+
"262": {}, // Djibouti Franc
110
+
"DOP": {}, // Dominican Peso
111
+
"214": {}, // Dominican Peso
112
+
"EGP": {}, // Egyptian Pound
113
+
"818": {}, // Egyptian Pound
114
+
"SVC": {}, // El Salvador Colon
115
+
"222": {}, // El Salvador Colon
116
+
"ERN": {}, // Nakfa
117
+
"232": {}, // Nakfa
118
+
"SZL": {}, // Lilangeni
119
+
"748": {}, // Lilangeni
120
+
"ETB": {}, // Ethiopian Birr
121
+
"230": {}, // Ethiopian Birr
122
+
"FKP": {}, // Falkland Islands Pound
123
+
"238": {}, // Falkland Islands Pound
124
+
"FJD": {}, // Fiji Dollar
125
+
"242": {}, // Fiji Dollar
126
+
"XPF": {}, // CFP Franc
127
+
"953": {}, // CFP Franc
128
+
"GMD": {}, // Dalasi
129
+
"270": {}, // Dalasi
130
+
"GEL": {}, // Lari
131
+
"981": {}, // Lari
132
+
"GHS": {}, // Ghana Cedi
133
+
"936": {}, // Ghana Cedi
134
+
"GIP": {}, // Gibraltar Pound
135
+
"292": {}, // Gibraltar Pound
136
+
"GTQ": {}, // Quetzal
137
+
"320": {}, // Quetzal
138
+
"GBP": {}, // Pound Sterling
139
+
"826": {}, // Pound Sterling
140
+
"GNF": {}, // Guinean Franc
141
+
"324": {}, // Guinean Franc
142
+
"GYD": {}, // Guyana Dollar
143
+
"328": {}, // Guyana Dollar
144
+
"HTG": {}, // Gourde
145
+
"332": {}, // Gourde
146
+
"HNL": {}, // Lempira
147
+
"340": {}, // Lempira
148
+
"HKD": {}, // Hong Kong Dollar
149
+
"344": {}, // Hong Kong Dollar
150
+
"HUF": {}, // Forint
151
+
"348": {}, // Forint
152
+
"ISK": {}, // Iceland Krona
153
+
"352": {}, // Iceland Krona
154
+
"IDR": {}, // Rupiah
155
+
"360": {}, // Rupiah
156
+
"XDR": {}, // SDR (Special Drawing Right)
157
+
"960": {}, // SDR (Special Drawing Right)
158
+
"IRR": {}, // Iranian Rial
159
+
"364": {}, // Iranian Rial
160
+
"IQD": {}, // Iraqi Dinar
161
+
"368": {}, // Iraqi Dinar
162
+
"ILS": {}, // New Israeli Sheqel
163
+
"376": {}, // New Israeli Sheqel
164
+
"JMD": {}, // Jamaican Dollar
165
+
"388": {}, // Jamaican Dollar
166
+
"JPY": {}, // Yen
167
+
"392": {}, // Yen
168
+
"JOD": {}, // Jordanian Dinar
169
+
"400": {}, // Jordanian Dinar
170
+
"KZT": {}, // Tenge
171
+
"398": {}, // Tenge
172
+
"KES": {}, // Kenyan Shilling
173
+
"404": {}, // Kenyan Shilling
174
+
"KPW": {}, // North Korean Won
175
+
"408": {}, // North Korean Won
176
+
"KRW": {}, // Won
177
+
"410": {}, // Won
178
+
"KWD": {}, // Kuwaiti Dinar
179
+
"414": {}, // Kuwaiti Dinar
180
+
"KGS": {}, // Som
181
+
"417": {}, // Som
182
+
"LAK": {}, // Lao Kip
183
+
"418": {}, // Lao Kip
184
+
"LBP": {}, // Lebanese Pound
185
+
"422": {}, // Lebanese Pound
186
+
"LSL": {}, // Loti
187
+
"426": {}, // Loti
188
+
"ZAR": {}, // Rand
189
+
"710": {}, // Rand
190
+
"LRD": {}, // Liberian Dollar
191
+
"430": {}, // Liberian Dollar
192
+
"LYD": {}, // Libyan Dinar
193
+
"434": {}, // Libyan Dinar
194
+
"CHF": {}, // Swiss Franc
195
+
"756": {}, // Swiss Franc
196
+
"MOP": {}, // Pataca
197
+
"446": {}, // Pataca
198
+
"MKD": {}, // Denar
199
+
"807": {}, // Denar
200
+
"MGA": {}, // Malagasy Ariary
201
+
"969": {}, // Malagasy Ariary
202
+
"MWK": {}, // Malawi Kwacha
203
+
"454": {}, // Malawi Kwacha
204
+
"MYR": {}, // Malaysian Ringgit
205
+
"458": {}, // Malaysian Ringgit
206
+
"MVR": {}, // Rufiyaa
207
+
"462": {}, // Rufiyaa
208
+
"MRU": {}, // Ouguiya
209
+
"929": {}, // Ouguiya
210
+
"MUR": {}, // Mauritius Rupee
211
+
"480": {}, // Mauritius Rupee
212
+
"XUA": {}, // ADB Unit of Account
213
+
"965": {}, // ADB Unit of Account
214
+
"MXN": {}, // Mexican Peso
215
+
"484": {}, // Mexican Peso
216
+
"MXV": {}, // Mexican Unidad de Inversion (UDI)
217
+
"979": {}, // Mexican Unidad de Inversion (UDI)
218
+
"MDL": {}, // Moldovan Leu
219
+
"498": {}, // Moldovan Leu
220
+
"MNT": {}, // Tugrik
221
+
"496": {}, // Tugrik
222
+
"MAD": {}, // Moroccan Dirham
223
+
"504": {}, // Moroccan Dirham
224
+
"MZN": {}, // Mozambique Metical
225
+
"943": {}, // Mozambique Metical
226
+
"MMK": {}, // Kyat
227
+
"104": {}, // Kyat
228
+
"NAD": {}, // Namibia Dollar
229
+
"516": {}, // Namibia Dollar
230
+
"NPR": {}, // Nepalese Rupee
231
+
"524": {}, // Nepalese Rupee
232
+
"NIO": {}, // Cordoba Oro
233
+
"558": {}, // Cordoba Oro
234
+
"NGN": {}, // Naira
235
+
"566": {}, // Naira
236
+
"OMR": {}, // Rial Omani
237
+
"512": {}, // Rial Omani
238
+
"PKR": {}, // Pakistan Rupee
239
+
"586": {}, // Pakistan Rupee
240
+
"PAB": {}, // Balboa
241
+
"590": {}, // Balboa
242
+
"PGK": {}, // Kina
243
+
"598": {}, // Kina
244
+
"PYG": {}, // Guarani
245
+
"600": {}, // Guarani
246
+
"PEN": {}, // Sol
247
+
"604": {}, // Sol
248
+
"PHP": {}, // Philippine Peso
249
+
"608": {}, // Philippine Peso
250
+
"PLN": {}, // Zloty
251
+
"985": {}, // Zloty
252
+
"QAR": {}, // Qatari Rial
253
+
"634": {}, // Qatari Rial
254
+
"RON": {}, // Romanian Leu
255
+
"946": {}, // Romanian Leu
256
+
"RUB": {}, // Russian Ruble
257
+
"643": {}, // Russian Ruble
258
+
"RWF": {}, // Rwanda Franc
259
+
"646": {}, // Rwanda Franc
260
+
"SHP": {}, // Saint Helena Pound
261
+
"654": {}, // Saint Helena Pound
262
+
"WST": {}, // Tala
263
+
"882": {}, // Tala
264
+
"STN": {}, // Dobra
265
+
"930": {}, // Dobra
266
+
"SAR": {}, // Saudi Riyal
267
+
"682": {}, // Saudi Riyal
268
+
"RSD": {}, // Serbian Dinar
269
+
"941": {}, // Serbian Dinar
270
+
"SCR": {}, // Seychelles Rupee
271
+
"690": {}, // Seychelles Rupee
272
+
"SLL": {}, // Leone
273
+
"694": {}, // Leone
274
+
"SGD": {}, // Singapore Dollar
275
+
"702": {}, // Singapore Dollar
276
+
"XSU": {}, // Sucre
277
+
"994": {}, // Sucre
278
+
"SBD": {}, // Solomon Islands Dollar
279
+
"090": {}, // Solomon Islands Dollar
280
+
"SOS": {}, // Somali Shilling
281
+
"706": {}, // Somali Shilling
282
+
"SSP": {}, // South Sudanese Pound
283
+
"728": {}, // South Sudanese Pound
284
+
"LKR": {}, // Sri Lanka Rupee
285
+
"144": {}, // Sri Lanka Rupee
286
+
"SDG": {}, // Sudanese Pound
287
+
"938": {}, // Sudanese Pound
288
+
"SRD": {}, // Surinam Dollar
289
+
"968": {}, // Surinam Dollar
290
+
"SEK": {}, // Swedish Krona
291
+
"752": {}, // Swedish Krona
292
+
"CHE": {}, // WIR Euro
293
+
"947": {}, // WIR Euro
294
+
"CHW": {}, // WIR Franc
295
+
"948": {}, // WIR Franc
296
+
"SYP": {}, // Syrian Pound
297
+
"760": {}, // Syrian Pound
298
+
"TWD": {}, // New Taiwan Dollar
299
+
"901": {}, // New Taiwan Dollar
300
+
"TJS": {}, // Somoni
301
+
"972": {}, // Somoni
302
+
"TZS": {}, // Tanzanian Shilling
303
+
"834": {}, // Tanzanian Shilling
304
+
"THB": {}, // Baht
305
+
"764": {}, // Baht
306
+
"TOP": {}, // Pa'anga
307
+
"776": {}, // Pa'anga
308
+
"TTD": {}, // Trinidad and Tobago Dollar
309
+
"780": {}, // Trinidad and Tobago Dollar
310
+
"TND": {}, // Tunisian Dinar
311
+
"788": {}, // Tunisian Dinar
312
+
"TRY": {}, // Turkish Lira
313
+
"949": {}, // Turkish Lira
314
+
"TMT": {}, // Turkmenistan New Manat
315
+
"934": {}, // Turkmenistan New Manat
316
+
"UGX": {}, // Uganda Shilling
317
+
"800": {}, // Uganda Shilling
318
+
"UAH": {}, // Hryvnia
319
+
"980": {}, // Hryvnia
320
+
"AED": {}, // UAE Dirham
321
+
"784": {}, // UAE Dirham
322
+
"USN": {}, // US Dollar (Next day)
323
+
"997": {}, // US Dollar (Next day)
324
+
"UYU": {}, // Peso Uruguayo
325
+
"858": {}, // Peso Uruguayo
326
+
"UYI": {}, // Uruguay Peso en Unidades Indexadas (UI)
327
+
"940": {}, // Uruguay Peso en Unidades Indexadas (UI)
328
+
"UYW": {}, // Unidad Previsional
329
+
"927": {}, // Unidad Previsional
330
+
"UZS": {}, // Uzbekistan Sum
331
+
"860": {}, // Uzbekistan Sum
332
+
"VUV": {}, // Vatu
333
+
"548": {}, // Vatu
334
+
"VES": {}, // Bolívar Soberano
335
+
"928": {}, // Bolívar Soberano
336
+
"VND": {}, // Dong
337
+
"704": {}, // Dong
338
+
"YER": {}, // Yemeni Rial
339
+
"886": {}, // Yemeni Rial
340
+
"ZMW": {}, // Zambian Kwacha
341
+
"967": {}, // Zambian Kwacha
342
+
"ZWL": {}, // Zimbabwe Dollar
343
+
"932": {}, // Zimbabwe Dollar
344
+
"ZWG": {}, // Zimbabwe Gold
345
+
"924": {}, // Zimbabwe Gold
346
+
"XBA": {}, // Bond Markets Unit European Composite Unit (EURCO)
347
+
"955": {}, // Bond Markets Unit European Composite Unit (EURCO)
348
+
"XBB": {}, // Bond Markets Unit European Monetary Unit (E.M.U.-6)
349
+
"956": {}, // Bond Markets Unit European Monetary Unit (E.M.U.-6)
350
+
"XBC": {}, // Bond Markets Unit European Unit of Account 9 (E.U.A.-9)
351
+
"957": {}, // Bond Markets Unit European Unit of Account 9 (E.U.A.-9)
352
+
"XBD": {}, // Bond Markets Unit European Unit of Account 17 (E.U.A.-17)
353
+
"958": {}, // Bond Markets Unit European Unit of Account 17 (E.U.A.-17)
354
+
"XTS": {}, // Codes specifically reserved for testing purposes
355
+
"963": {}, // Codes specifically reserved for testing purposes
356
+
"XXX": {}, // The codes assigned for transactions where no currency is involved
357
+
"999": {}, // The codes assigned for transactions where no currency is involved
358
+
"XAU": {}, // Gold
359
+
"959": {}, // Gold
360
+
"XPD": {}, // Palladium
361
+
"964": {}, // Palladium
362
+
"XPT": {}, // Platinum
363
+
"962": {}, // Platinum
364
+
"XAG": {}, // Silver
365
+
"961": {}, // Silver
366
+
}
367
+
368
+
func Lookup(code string) bool {
369
+
_, exists := lookupTable[code]
370
+
return exists
371
+
}
+72
internal/domain/payment.go
+72
internal/domain/payment.go
···
1
+
package domain
2
+
3
+
import (
4
+
"errors"
5
+
"regexp"
6
+
"sync"
7
+
"time"
8
+
9
+
"github.com/Tulkdan/payment-gateway/internal/constants"
10
+
)
11
+
12
+
type Status string
13
+
14
+
const (
15
+
StatusPending Status = "pending" // when it needs to send payment to provider
16
+
StatusApproved Status = "approved" // when provider successfully charged
17
+
StatusRejected Status = "rejected" // when provider rejected payment
18
+
StatusFailed Status = "failed" // when provider fails to charge
19
+
)
20
+
21
+
type PaymentCard struct {
22
+
Number string
23
+
HolderName string
24
+
CVV string
25
+
ExpirationDate string
26
+
Installments uint
27
+
}
28
+
29
+
type Payment struct {
30
+
Amount uint
31
+
Currency string
32
+
Description string
33
+
PaymentType string
34
+
Card PaymentCard
35
+
Status Status
36
+
CreatedAt time.Time
37
+
38
+
mu sync.Mutex
39
+
}
40
+
41
+
func NewPayment(amount uint, currency string, description string, paymentType string, card PaymentCard) (*Payment, error) {
42
+
if paymentType != "card" {
43
+
return nil, errors.New("We only accept payments with type 'card'")
44
+
}
45
+
46
+
isoFormatRgx := regexp.MustCompile(`^[A-Z]{3}$`)
47
+
if !isoFormatRgx.Match([]byte(currency)) || !constants.Lookup(currency) {
48
+
return nil, errors.New("Invalid Currency")
49
+
}
50
+
51
+
expirationDateRgx := regexp.MustCompile(`\d{2}\/\d{4}`)
52
+
if !expirationDateRgx.Match([]byte(card.ExpirationDate)) {
53
+
return nil, errors.New("Invalid ExpirationDate format")
54
+
}
55
+
56
+
return &Payment{
57
+
Amount: amount,
58
+
Currency: currency,
59
+
Description: description,
60
+
PaymentType: paymentType,
61
+
Card: card,
62
+
Status: StatusPending,
63
+
CreatedAt: time.Now(),
64
+
}, nil
65
+
}
66
+
67
+
func (p *Payment) UpdateStatus(status Status) {
68
+
p.mu.Lock()
69
+
defer p.mu.Unlock()
70
+
71
+
p.Status = status
72
+
}
+65
internal/domain/payment_test.go
+65
internal/domain/payment_test.go
···
1
+
package domain_test
2
+
3
+
import (
4
+
"reflect"
5
+
"testing"
6
+
"time"
7
+
8
+
"github.com/Tulkdan/payment-gateway/internal/domain"
9
+
)
10
+
11
+
func TestPayment(t *testing.T) {
12
+
t.Run("should validate currency in type ISO 4217", func(t *testing.T) {
13
+
currency := "R$"
14
+
15
+
_, got := domain.NewPayment(1000, currency, "", "card", domain.PaymentCard{Number: "", HolderName: "", CVV: "", ExpirationDate: "02/2025", Installments: 1})
16
+
want := "Invalid Currency"
17
+
18
+
if got.Error() != want {
19
+
t.Errorf("got %q want %q given %q", got, want, currency)
20
+
}
21
+
})
22
+
23
+
t.Run("should validate paymentType received as 'card'", func(t *testing.T) {
24
+
paymentType := "2025/02"
25
+
26
+
_, got := domain.NewPayment(1000, "R$", "", paymentType, domain.PaymentCard{Number: "", HolderName: "", CVV: "", ExpirationDate: "02/2025", Installments: 1})
27
+
want := "We only accept payments with type 'card'"
28
+
29
+
if got.Error() != want {
30
+
t.Errorf("got %q want %q given %q", got, want, paymentType)
31
+
}
32
+
})
33
+
34
+
t.Run("should validate expirationDate from card to be in format DD/YYYY", func(t *testing.T) {
35
+
expirationDate := "2025/02"
36
+
37
+
_, got := domain.NewPayment(1000, "BRL", "", "card", domain.PaymentCard{Number: "", HolderName: "", CVV: "", ExpirationDate: expirationDate, Installments: 1})
38
+
want := "Invalid ExpirationDate format"
39
+
40
+
if got.Error() != want {
41
+
t.Errorf("got %q want %q given %q", got, want, expirationDate)
42
+
}
43
+
})
44
+
45
+
t.Run("should receive a Currency passing data", func(t *testing.T) {
46
+
got, err := domain.NewPayment(1000, "BRL", "", "card", domain.PaymentCard{Number: "", HolderName: "", CVV: "", ExpirationDate: "02/2025", Installments: 1})
47
+
want := &domain.Payment{
48
+
Amount: 1000,
49
+
Currency: "BRL",
50
+
Description: "",
51
+
PaymentType: "card",
52
+
Card: domain.PaymentCard{Number: "", HolderName: "", CVV: "", ExpirationDate: "02/2025", Installments: 1},
53
+
Status: "pending",
54
+
CreatedAt: time.Now(),
55
+
}
56
+
57
+
if err != nil {
58
+
t.Fatal("got an error but didn't want one")
59
+
}
60
+
61
+
if reflect.DeepEqual(got, want) {
62
+
t.Errorf("got %q want %q", got, want)
63
+
}
64
+
})
65
+
}
+15
internal/domain/provider.go
+15
internal/domain/provider.go
+21
internal/dto/payments.go
+21
internal/dto/payments.go
···
1
+
package dto
2
+
3
+
type PaymentCardInput struct {
4
+
Number string `json:"number"`
5
+
HolderName string `json:"holderName"`
6
+
CVV string `json:"cvv"`
7
+
ExpirationDate string `json:"expirationDate"`
8
+
Installments uint `json:"installments"`
9
+
}
10
+
11
+
type PaymentInput struct {
12
+
Amount uint `json:"amount"`
13
+
Currency string `json:"currency"`
14
+
Description string `json:"description"`
15
+
PaymentType string `json:"paymentType"`
16
+
Card PaymentCardInput `json:"card"`
17
+
}
18
+
19
+
type PaymentOutput struct {
20
+
Message string `json:"message"`
21
+
}
+110
internal/providers/braintree.go
+110
internal/providers/braintree.go
···
1
+
package providers
2
+
3
+
import (
4
+
"bytes"
5
+
"encoding/json"
6
+
"net/http"
7
+
8
+
"github.com/Tulkdan/payment-gateway/internal/domain"
9
+
"github.com/google/uuid"
10
+
)
11
+
12
+
type BraintreeProvider struct {
13
+
Url string
14
+
}
15
+
16
+
func NewBraintreeProvider(url string) *BraintreeProvider {
17
+
return &BraintreeProvider{Url: url}
18
+
}
19
+
20
+
type BraintreeChargeCard struct {
21
+
Number string `json:"number"`
22
+
HolderName string `json:"holderName"`
23
+
CVV string `json:"cvv"`
24
+
ExpirationDate string `json:"expirationDate"`
25
+
Installments uint `json:"installments"`
26
+
}
27
+
28
+
type BraintreeChargePaymentMethod struct {
29
+
Type string `json:"type"`
30
+
Card BraintreeChargeCard `json:"card"`
31
+
}
32
+
33
+
type BraintreeCharge struct {
34
+
Amount uint `json:"amount"`
35
+
Currency string `json:"currency"`
36
+
Description string `json:"description"`
37
+
PaymentMethod BraintreeChargePaymentMethod `json:"paymentMethod"`
38
+
}
39
+
40
+
func (b BraintreeProvider) Charge(request *domain.Payment) (*domain.Provider, error) {
41
+
body := b.createChargeBody(request)
42
+
response, err := http.Post(b.Url, "application/json", bytes.NewBuffer(body))
43
+
if err != nil {
44
+
return nil, err
45
+
}
46
+
47
+
return b.responseCharge(response)
48
+
}
49
+
50
+
func (b BraintreeProvider) createChargeBody(request *domain.Payment) []byte {
51
+
toSend := &BraintreeCharge{
52
+
Amount: request.Amount,
53
+
Currency: request.Currency,
54
+
Description: request.Description,
55
+
PaymentMethod: BraintreeChargePaymentMethod{
56
+
Type: request.PaymentType,
57
+
Card: BraintreeChargeCard{
58
+
Number: request.Card.Number,
59
+
HolderName: request.Card.HolderName,
60
+
CVV: request.Card.CVV,
61
+
ExpirationDate: request.Card.ExpirationDate,
62
+
Installments: request.Card.Installments,
63
+
},
64
+
},
65
+
}
66
+
jsonValue, _ := json.Marshal(toSend)
67
+
return jsonValue
68
+
}
69
+
70
+
type BraintreeChargeResponse struct {
71
+
Id uuid.UUID `json:"id"`
72
+
CreatedAt string `json:"createdAt"`
73
+
Status string `json:"status"` // authorized failed refunded
74
+
OriginalAmount uint `json:"originalAmount"`
75
+
CurrentAmount uint `json:"currentAmount"`
76
+
Currency string `json:"currency"`
77
+
Description string `json:"description"`
78
+
PaymentMethod string `json:"paymentMethod"`
79
+
CardId uuid.UUID `json:"cardId"`
80
+
}
81
+
82
+
func (b BraintreeProvider) responseCharge(response *http.Response) (*domain.Provider, error) {
83
+
var data BraintreeChargeResponse
84
+
if err := json.NewDecoder(response.Body).Decode(&data); err != nil {
85
+
return nil, err
86
+
}
87
+
88
+
var status domain.Status
89
+
switch data.Status {
90
+
case "authorized":
91
+
status = domain.StatusApproved
92
+
case "failed":
93
+
status = domain.StatusFailed
94
+
case "refunded":
95
+
status = domain.StatusRejected
96
+
}
97
+
98
+
providerResponse := &domain.Provider{
99
+
Id: data.Id,
100
+
CreatedAt: data.CreatedAt,
101
+
OriginalAmount: data.OriginalAmount,
102
+
CurrentAmount: data.CurrentAmount,
103
+
Currency: data.Currency,
104
+
Description: data.Description,
105
+
PaymentMethod: data.PaymentMethod,
106
+
CardId: data.CardId,
107
+
Status: status,
108
+
}
109
+
return providerResponse, nil
110
+
}
+74
internal/providers/braintree_test.go
+74
internal/providers/braintree_test.go
···
1
+
package providers_test
2
+
3
+
import (
4
+
"encoding/json"
5
+
"net/http"
6
+
"net/http/httptest"
7
+
"reflect"
8
+
"testing"
9
+
"time"
10
+
11
+
"github.com/Tulkdan/payment-gateway/internal/domain"
12
+
"github.com/Tulkdan/payment-gateway/internal/providers"
13
+
"github.com/google/uuid"
14
+
)
15
+
16
+
func TestBraintree(t *testing.T) {
17
+
t.Run("should make request to url", func(t *testing.T) {
18
+
id, _ := uuid.Parse("2ee70bcb-5cb9-4412-a35f-c2a15fb88ef1")
19
+
cardId, _ := uuid.Parse("ed6ecd4c-81d5-4e63-bb12-99439ae559e7")
20
+
serverResponse := &providers.BraintreeChargeResponse{
21
+
Id: id,
22
+
CreatedAt: time.Now().Format("YYYY-MM-DD"),
23
+
Status: "authorized",
24
+
OriginalAmount: 1000,
25
+
CurrentAmount: 1000,
26
+
Currency: "BRL",
27
+
Description: "",
28
+
PaymentMethod: "card",
29
+
CardId: cardId,
30
+
}
31
+
32
+
server := createServerBraintree(serverResponse)
33
+
defer server.Close()
34
+
35
+
charge := &domain.Payment{
36
+
Amount: 1000,
37
+
Currency: "BRL",
38
+
Description: "",
39
+
PaymentType: "card",
40
+
Card: domain.PaymentCard{Number: "", HolderName: "", CVV: "", ExpirationDate: "02/2025", Installments: 1},
41
+
Status: "pending",
42
+
CreatedAt: time.Now(),
43
+
}
44
+
want := &domain.Provider{
45
+
Id: id,
46
+
CreatedAt: time.Now().Format("YYYY-MM-DD"),
47
+
Status: domain.StatusApproved,
48
+
OriginalAmount: 1000,
49
+
CurrentAmount: 1000,
50
+
Currency: "BRL",
51
+
Description: "",
52
+
PaymentMethod: "card",
53
+
CardId: cardId,
54
+
}
55
+
56
+
provider := providers.NewBraintreeProvider(server.URL)
57
+
response, err := provider.Charge(charge)
58
+
59
+
if err != nil {
60
+
t.Fatalf("got an error but didn't want one %q", err)
61
+
}
62
+
63
+
if !reflect.DeepEqual(response, want) {
64
+
t.Errorf("got %q want %q", response, want)
65
+
}
66
+
})
67
+
}
68
+
69
+
func createServerBraintree(serverResponse *providers.BraintreeChargeResponse) *httptest.Server {
70
+
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
71
+
w.WriteHeader(http.StatusOK)
72
+
json.NewEncoder(w).Encode(serverResponse)
73
+
}))
74
+
}
+60
internal/providers/provider.go
+60
internal/providers/provider.go
···
1
+
package providers
2
+
3
+
import (
4
+
"errors"
5
+
"time"
6
+
7
+
"github.com/Tulkdan/payment-gateway/internal/domain"
8
+
)
9
+
10
+
var thirtySecondTimout = 30 * time.Second
11
+
12
+
type Provider interface {
13
+
Charge(request *domain.Payment) (*domain.Provider, error)
14
+
}
15
+
16
+
type UseProviders struct {
17
+
providers []Provider
18
+
timeout time.Duration
19
+
}
20
+
21
+
func NewUseProviders(providers []Provider) *UseProviders {
22
+
return ConfigurableUseProvider(providers, thirtySecondTimout)
23
+
}
24
+
25
+
func ConfigurableUseProvider(providers []Provider, timeout time.Duration) *UseProviders {
26
+
return &UseProviders{
27
+
providers: providers,
28
+
timeout: timeout,
29
+
}
30
+
}
31
+
32
+
func (p *UseProviders) Payment(payment *domain.Payment) (*domain.Provider, error) {
33
+
var err error = nil
34
+
35
+
for _, provider := range p.providers {
36
+
select {
37
+
case data := <-charge(payment, provider):
38
+
return data, nil
39
+
case <-time.After(p.timeout):
40
+
err = errors.New("Timeout")
41
+
continue
42
+
}
43
+
}
44
+
45
+
return nil, err
46
+
}
47
+
48
+
func charge(charge *domain.Payment, provider Provider) chan *domain.Provider {
49
+
ch := make(chan *domain.Provider)
50
+
51
+
go func() {
52
+
response, err := provider.Charge(charge)
53
+
if err != nil {
54
+
close(ch)
55
+
}
56
+
ch <- response
57
+
}()
58
+
59
+
return ch
60
+
}
+103
internal/providers/provider_test.go
+103
internal/providers/provider_test.go
···
1
+
package providers_test
2
+
3
+
import (
4
+
"testing"
5
+
"time"
6
+
7
+
"github.com/Tulkdan/payment-gateway/internal/domain"
8
+
"github.com/Tulkdan/payment-gateway/internal/providers"
9
+
)
10
+
11
+
type SpyProvider struct {
12
+
Calls uint
13
+
Timeout time.Duration
14
+
Response *domain.Provider
15
+
}
16
+
17
+
func (s *SpyProvider) Charge(request *domain.Payment) (*domain.Provider, error) {
18
+
time.Sleep(s.Timeout)
19
+
s.Calls++
20
+
21
+
return s.Response, nil
22
+
}
23
+
24
+
func TestProvider(t *testing.T) {
25
+
t.Run("should make request for first provider", func(t *testing.T) {
26
+
spyFirst := &SpyProvider{Timeout: 10 * time.Millisecond, Response: &domain.Provider{Description: "First"}}
27
+
spySecond := &SpyProvider{Timeout: 10 * time.Millisecond, Response: &domain.Provider{Description: "Second"}}
28
+
29
+
payment, _ := domain.NewPayment(1000, "R$", "", "card", domain.PaymentCard{Number: "", HolderName: "", CVV: "", ExpirationDate: "02/2025", Installments: 1})
30
+
31
+
useProvider := providers.ConfigurableUseProvider([]providers.Provider{spyFirst, spySecond}, 15*time.Millisecond)
32
+
data, err := useProvider.Payment(payment)
33
+
34
+
if err != nil {
35
+
t.Fatal("Got error. didn't want one")
36
+
}
37
+
38
+
if data != spyFirst.Response {
39
+
t.Fatalf("expected data to be %q, got %q", spyFirst.Response, data)
40
+
}
41
+
42
+
assertSpyCalled(t, spyFirst, "spyFirst", 1)
43
+
assertSpyNotCalled(t, spySecond, "spySecond")
44
+
})
45
+
46
+
t.Run("should make request for second provider when first provider timeouts", func(t *testing.T) {
47
+
spyFirst := &SpyProvider{Timeout: 20 * time.Millisecond, Response: &domain.Provider{Description: "First"}}
48
+
spySecond := &SpyProvider{Timeout: 10 * time.Millisecond, Response: &domain.Provider{Description: "Second"}}
49
+
50
+
payment, _ := domain.NewPayment(1000, "R$", "", "card", domain.PaymentCard{Number: "", HolderName: "", CVV: "", ExpirationDate: "02/2025", Installments: 1})
51
+
52
+
useProvider := providers.ConfigurableUseProvider([]providers.Provider{spyFirst, spySecond}, 15*time.Millisecond)
53
+
data, err := useProvider.Payment(payment)
54
+
55
+
if err != nil {
56
+
t.Fatal("Got error. didn't want one")
57
+
}
58
+
59
+
if data != spySecond.Response {
60
+
t.Fatalf("expected data to be %q, got %q", spySecond.Response, data)
61
+
}
62
+
63
+
assertSpyCalled(t, spyFirst, "spyFirst", 1)
64
+
assertSpyCalled(t, spySecond, "spySecond", 1)
65
+
})
66
+
67
+
t.Run("should return error when all providers timeout", func(t *testing.T) {
68
+
spyFirst := &SpyProvider{Timeout: 20 * time.Millisecond, Response: &domain.Provider{Description: "First"}}
69
+
spySecond := &SpyProvider{Timeout: 20 * time.Millisecond, Response: &domain.Provider{Description: "Second"}}
70
+
71
+
payment, _ := domain.NewPayment(1000, "R$", "", "card", domain.PaymentCard{Number: "", HolderName: "", CVV: "", ExpirationDate: "02/2025", Installments: 1})
72
+
73
+
useProvider := providers.ConfigurableUseProvider([]providers.Provider{spyFirst, spySecond}, 5*time.Millisecond)
74
+
data, err := useProvider.Payment(payment)
75
+
76
+
if data != nil {
77
+
t.Fatalf("Got data but didn't expected one, got %q", data)
78
+
}
79
+
80
+
if err.Error() != "Timeout" {
81
+
t.Fatalf("expected error to be %s, got %s", "Timeout", err.Error())
82
+
}
83
+
84
+
assertSpyNotCalled(t, spyFirst, "spyFirst")
85
+
assertSpyNotCalled(t, spySecond, "spySecond")
86
+
})
87
+
}
88
+
89
+
func assertSpyCalled(t testing.TB, spy *SpyProvider, name string, wantTimes uint) {
90
+
t.Helper()
91
+
92
+
if spy.Calls != wantTimes {
93
+
t.Fatalf("not enough calls to %s, want %d got %d", name, wantTimes, spy.Calls)
94
+
}
95
+
}
96
+
97
+
func assertSpyNotCalled(t testing.TB, spy *SpyProvider, name string) {
98
+
t.Helper()
99
+
100
+
if spy.Calls != 0 {
101
+
t.Fatalf("%s has been called, want 0 got %d", name, spy.Calls)
102
+
}
103
+
}
+104
internal/providers/stripe.go
+104
internal/providers/stripe.go
···
1
+
package providers
2
+
3
+
import (
4
+
"bytes"
5
+
"encoding/json"
6
+
"net/http"
7
+
8
+
"github.com/Tulkdan/payment-gateway/internal/domain"
9
+
"github.com/google/uuid"
10
+
)
11
+
12
+
type StripeProvider struct {
13
+
Url string
14
+
}
15
+
16
+
func NewStripeProvider(url string) *StripeProvider {
17
+
return &StripeProvider{Url: url}
18
+
}
19
+
20
+
type StripeChargeCard struct {
21
+
Number string `json:"number"`
22
+
HolderName string `json:"holder"`
23
+
CVV string `json:"cvv"`
24
+
ExpirationDate string `json:"expiration"`
25
+
Installments uint `json:"installmentNumber"`
26
+
}
27
+
28
+
type StripeCharge struct {
29
+
Amount uint `json:"amount"`
30
+
Currency string `json:"currency"`
31
+
Description string `json:"statementDescriptor"`
32
+
PaymentType string `json:"paymentType"`
33
+
Card StripeChargeCard `json:"card"`
34
+
}
35
+
36
+
func (b StripeProvider) Charge(request *domain.Payment) (*domain.Provider, error) {
37
+
body := b.createChargeBody(request)
38
+
response, err := http.Post(b.Url, "application/json", bytes.NewBuffer(body))
39
+
if err != nil {
40
+
return nil, err
41
+
}
42
+
43
+
return b.responseCharge(response)
44
+
}
45
+
46
+
func (b StripeProvider) createChargeBody(request *domain.Payment) []byte {
47
+
toSend := &StripeCharge{
48
+
Amount: request.Amount,
49
+
Currency: request.Currency,
50
+
Description: request.Description,
51
+
PaymentType: request.PaymentType,
52
+
Card: StripeChargeCard{
53
+
Number: request.Card.Number,
54
+
HolderName: request.Card.HolderName,
55
+
CVV: request.Card.CVV,
56
+
ExpirationDate: request.Card.ExpirationDate,
57
+
Installments: request.Card.Installments,
58
+
},
59
+
}
60
+
jsonValue, _ := json.Marshal(toSend)
61
+
return jsonValue
62
+
}
63
+
64
+
type StripeChargeResponse struct {
65
+
Id uuid.UUID `json:"id"`
66
+
CreatedAt string `json:"date"`
67
+
Status string `json:"status"` // paid failed voided
68
+
OriginalAmount uint `json:"originalAmount"`
69
+
CurrentAmount uint `json:"amount"`
70
+
Currency string `json:"currency"`
71
+
Description string `json:"statementDescriptor"`
72
+
PaymentMethod string `json:"paymentMethod"`
73
+
CardId uuid.UUID `json:"cardId"`
74
+
}
75
+
76
+
func (b StripeProvider) responseCharge(response *http.Response) (*domain.Provider, error) {
77
+
var data StripeChargeResponse
78
+
if err := json.NewDecoder(response.Body).Decode(&data); err != nil {
79
+
return nil, err
80
+
}
81
+
82
+
var status domain.Status
83
+
switch data.Status {
84
+
case "paid":
85
+
status = domain.StatusApproved
86
+
case "failed":
87
+
status = domain.StatusFailed
88
+
case "voided":
89
+
status = domain.StatusRejected
90
+
}
91
+
92
+
providerResponse := &domain.Provider{
93
+
Id: data.Id,
94
+
CreatedAt: data.CreatedAt,
95
+
OriginalAmount: data.OriginalAmount,
96
+
CurrentAmount: data.CurrentAmount,
97
+
Currency: data.Currency,
98
+
Description: data.Description,
99
+
PaymentMethod: data.PaymentMethod,
100
+
CardId: data.CardId,
101
+
Status: status,
102
+
}
103
+
return providerResponse, nil
104
+
}
+74
internal/providers/stripe_test.go
+74
internal/providers/stripe_test.go
···
1
+
package providers_test
2
+
3
+
import (
4
+
"encoding/json"
5
+
"net/http"
6
+
"net/http/httptest"
7
+
"reflect"
8
+
"testing"
9
+
"time"
10
+
11
+
"github.com/Tulkdan/payment-gateway/internal/domain"
12
+
"github.com/Tulkdan/payment-gateway/internal/providers"
13
+
"github.com/google/uuid"
14
+
)
15
+
16
+
func TestStripe(t *testing.T) {
17
+
t.Run("should make request to url", func(t *testing.T) {
18
+
id, _ := uuid.Parse("2ee70bcb-5cb9-4412-a35f-c2a15fb88ef1")
19
+
cardId, _ := uuid.Parse("ed6ecd4c-81d5-4e63-bb12-99439ae559e7")
20
+
serverResponse := &providers.StripeChargeResponse{
21
+
Id: id,
22
+
CreatedAt: time.Now().Format("YYYY-MM-DD"),
23
+
Status: "paid",
24
+
OriginalAmount: 1000,
25
+
CurrentAmount: 1000,
26
+
Currency: "BRL",
27
+
Description: "",
28
+
PaymentMethod: "card",
29
+
CardId: cardId,
30
+
}
31
+
32
+
server := createServerStripe(serverResponse)
33
+
defer server.Close()
34
+
35
+
charge := &domain.Payment{
36
+
Amount: 1000,
37
+
Currency: "BRL",
38
+
Description: "",
39
+
PaymentType: "card",
40
+
Card: domain.PaymentCard{Number: "", HolderName: "", CVV: "", ExpirationDate: "02/2025", Installments: 1},
41
+
Status: "pending",
42
+
CreatedAt: time.Now(),
43
+
}
44
+
want := &domain.Provider{
45
+
Id: id,
46
+
CreatedAt: time.Now().Format("YYYY-MM-DD"),
47
+
Status: domain.StatusApproved,
48
+
OriginalAmount: 1000,
49
+
CurrentAmount: 1000,
50
+
Currency: "BRL",
51
+
Description: "",
52
+
PaymentMethod: "card",
53
+
CardId: cardId,
54
+
}
55
+
56
+
provider := providers.NewStripeProvider(server.URL)
57
+
response, err := provider.Charge(charge)
58
+
59
+
if err != nil {
60
+
t.Fatalf("got an error but didn't want one %q", err)
61
+
}
62
+
63
+
if !reflect.DeepEqual(response, want) {
64
+
t.Errorf("got %q want %q", response, want)
65
+
}
66
+
})
67
+
}
68
+
69
+
func createServerStripe(serverResponse *providers.StripeChargeResponse) *httptest.Server {
70
+
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
71
+
w.WriteHeader(http.StatusOK)
72
+
json.NewEncoder(w).Encode(serverResponse)
73
+
}))
74
+
}
+26
internal/service/payment_service.go
+26
internal/service/payment_service.go
···
1
+
package service
2
+
3
+
import (
4
+
"context"
5
+
"fmt"
6
+
7
+
"github.com/Tulkdan/payment-gateway/internal/domain"
8
+
"github.com/Tulkdan/payment-gateway/internal/dto"
9
+
)
10
+
11
+
type PaymentService struct{}
12
+
13
+
func NewPaymentService() *PaymentService {
14
+
return &PaymentService{}
15
+
}
16
+
17
+
func (p *PaymentService) CreatePayment(ctx context.Context, input dto.PaymentInput) (*dto.PaymentOutput, error) {
18
+
payment, err := domain.NewPayment(input.Amount, input.Currency, input.Description, input.PaymentType, domain.PaymentCard(input.Card))
19
+
if err != nil {
20
+
return nil, err
21
+
}
22
+
23
+
fmt.Printf("%+v", payment)
24
+
25
+
return &dto.PaymentOutput{Message: "Processed successfully"}, nil
26
+
}
+37
internal/web/handler/payments_handler.go
+37
internal/web/handler/payments_handler.go
···
1
+
package handler
2
+
3
+
import (
4
+
"encoding/json"
5
+
"net/http"
6
+
7
+
"github.com/Tulkdan/payment-gateway/internal/dto"
8
+
"github.com/Tulkdan/payment-gateway/internal/service"
9
+
)
10
+
11
+
type PaymentsHandler struct {
12
+
paymentService *service.PaymentService
13
+
}
14
+
15
+
func NewPaymentsHandler(paymentsService *service.PaymentService) *PaymentsHandler {
16
+
return &PaymentsHandler{
17
+
paymentService: paymentsService,
18
+
}
19
+
}
20
+
21
+
func (p *PaymentsHandler) Create(w http.ResponseWriter, r *http.Request) {
22
+
var body dto.PaymentInput
23
+
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
24
+
http.Error(w, err.Error(), http.StatusBadRequest)
25
+
return
26
+
}
27
+
28
+
response, err := p.paymentService.CreatePayment(r.Context(), body)
29
+
if err != nil {
30
+
http.Error(w, err.Error(), http.StatusBadRequest)
31
+
return
32
+
}
33
+
34
+
w.Header().Set("Content-Type", "application/json")
35
+
w.WriteHeader(http.StatusOK)
36
+
json.NewEncoder(w).Encode(response)
37
+
}
+46
internal/web/server.go
+46
internal/web/server.go
···
1
+
package web
2
+
3
+
import (
4
+
"net/http"
5
+
6
+
"github.com/Tulkdan/payment-gateway/internal/service"
7
+
"github.com/Tulkdan/payment-gateway/internal/web/handler"
8
+
)
9
+
10
+
type Server struct {
11
+
port string
12
+
router *http.ServeMux
13
+
server *http.Server
14
+
15
+
paymentsService *service.PaymentService
16
+
}
17
+
18
+
func NewServer(paymentsService *service.PaymentService, port string) *Server {
19
+
return &Server{
20
+
port: port,
21
+
paymentsService: paymentsService,
22
+
}
23
+
}
24
+
25
+
func (s *Server) ConfigureRouter() {
26
+
r := &http.ServeMux{}
27
+
28
+
paymentsHandler := handler.NewPaymentsHandler(s.paymentsService)
29
+
30
+
r.HandleFunc("POST /payments", paymentsHandler.Create)
31
+
// r.HandleFunc("POST /refunds", func(http.ResponseWriter, *http.Request) {})
32
+
// r.HandleFunc("GET /payments/{id}", func(w http.ResponseWriter, r *http.Request) {
33
+
// id := r.PathValue("id")
34
+
// })
35
+
36
+
s.router = r
37
+
}
38
+
39
+
func (s *Server) Start() error {
40
+
s.server = &http.Server{
41
+
Addr: ":" + s.port,
42
+
Handler: s.router,
43
+
}
44
+
45
+
return s.server.ListenAndServe()
46
+
}