summaryrefslogtreecommitdiff
path: root/c2ec/wallee-client.go
blob: 87b48cc557625fb62e7b920c9d4b288addef6938 (plain)
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
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
package main

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha512"
	"encoding/base64"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"
)

const WALLEE_AUTH_HEADER_VERSION = "x-mac-version"
const WALLEE_AUTH_HEADER_USERID = "x-mac-userid"
const WALLEE_AUTH_HEADER_TIMESTAMP = "x-mac-timestamp"
const WALLEE_AUTH_HEADER_MAC = "x-mac-value"

const WALLEE_READ_TRANSACTION_API = "/api/transaction/read"
const WALLEE_CREATE_REFUND_API = "/api/refund/refund"

const WALLEE_API_SPACEID_PARAM_NAME = "spaceId"

type WalleeCredentials struct {
	SpaceId            int    `json:"spaceId"`
	UserId             int    `json:"userId"`
	ApplicationUserKey string `json:"application-user-key"`
}

type WalleeClient struct {
	ProviderClient

	name        string
	baseUrl     string
	credentials *WalleeCredentials
}

func (wt *WalleeTransaction) AllowWithdrawal() bool {

	return strings.EqualFold(string(wt.State), string(StateFulfill))
}

func (w *WalleeClient) SetupClient(p *Provider) error {

	cfg, err := ConfigForProvider(p.Name)
	if err != nil {
		return err
	}

	creds, err := parseCredentials(p.BackendCredentials, cfg)
	if err != nil {
		return err
	}

	w.name = p.Name
	w.baseUrl = p.BackendBaseURL
	w.credentials = creds

	PROVIDER_CLIENTS[w.name] = w

	return nil
}

func (w *WalleeClient) GetTransaction(transactionId string) (ProviderTransaction, error) {

	call := WALLEE_READ_TRANSACTION_API
	queryParams := map[string]string{
		WALLEE_API_SPACEID_PARAM_NAME: strconv.Itoa(w.credentials.SpaceId),
		"id":                          transactionId,
	}
	url := FormatUrl(call, map[string]string{}, queryParams)

	hdrs, err := w.prepareWalleeHeaders(url, HTTP_GET)
	if err != nil {
		return nil, err
	}

	t, status, err := HttpGet(url, hdrs, NewJsonCodec[WalleeTransaction]())
	if err != nil {
		return nil, err
	}
	if status != HTTP_OK {
		return nil, errors.New("no result")
	}
	return t, nil
}

func (w *WalleeClient) Refund(transactionId string) error {
	return errors.New("not yet implemented")
}

func (w *WalleeClient) prepareWalleeHeaders(url string, method string) (map[string]string, error) {

	timestamp := time.Time.Unix(time.Now())

	base64Mac, err := calculateWalleeAuthToken(
		w.credentials.UserId,
		timestamp,
		method,
		url,
		w.credentials.ApplicationUserKey,
	)
	if err != nil {
		return nil, err
	}

	headers := map[string]string{
		WALLEE_AUTH_HEADER_VERSION:   "1",
		WALLEE_AUTH_HEADER_USERID:    strconv.Itoa(w.credentials.UserId),
		WALLEE_AUTH_HEADER_TIMESTAMP: strconv.Itoa(int(timestamp)),
		WALLEE_AUTH_HEADER_MAC:       base64Mac,
	}

	return headers, nil
}

func parseCredentials(raw string, cfg *C2ECProviderConfig) (*WalleeCredentials, error) {

	credsJson := make([]byte, len(raw))
	_, err := base64.StdEncoding.Decode(credsJson, []byte(raw))
	if err != nil {
		return nil, err
	}

	creds, err := NewJsonCodec[WalleeCredentials]().Decode(bytes.NewBuffer(credsJson))
	if err != nil {
		return nil, err
	}

	if !ValidPassword(cfg.CredentialsPassword, creds.ApplicationUserKey) {
		return nil, errors.New("invalid application user key")
	}

	// correct application user key.
	creds.ApplicationUserKey = cfg.CredentialsPassword
	return creds, nil
}

// This function calculates the authentication token according
// to the documentation of wallee:
// https://app-wallee.com/en-us/doc/api/web-service#_authentication
// the function returns the token in Base64 format.
func calculateWalleeAuthToken(
	userId int,
	unixTimestamp int64,
	httpMethod string,
	pathWithParams string,
	userKeyBase64 string,
) (string, error) {

	// Put together the correct formatted string
	// Version | UserId | Timestamp | Method | Path
	authMsgStr := fmt.Sprintf("%d|%d|%d|%s|%s",
		1, // version is static
		userId,
		unixTimestamp,
		httpMethod,
		pathWithParams,
	)

	authMsg := make([]byte, 0)
	if valid := utf8.ValidString(authMsgStr); !valid {

		// encode the string using utf8
		for _, r := range authMsgStr {
			rbytes := make([]byte, 4)
			utf8.EncodeRune(rbytes, r)
			authMsg = append(authMsg, rbytes...)
		}
	}

	key := make([]byte, base64.StdEncoding.DecodedLen(len(userKeyBase64)))
	_, err := base64.StdEncoding.Decode(key, []byte(userKeyBase64))
	if err != nil {
		LogError("wallee-client", err)
		return "", err
	}

	if len(key) != 32 {
		return "", errors.New("malformed secret")
	}

	macer := hmac.New(sha512.New, key)
	_, err = macer.Write(authMsg)
	if err != nil {
		LogError("wallee-client", err)
		return "", err
	}
	mac := make([]byte, 64)
	mac = macer.Sum(mac)

	return base64.StdEncoding.EncodeToString(mac), nil
}

type TransactionState string

const (
	StateCreate     TransactionState = "CREATE"
	StatePending    TransactionState = "PENDING"
	StateConfirmed  TransactionState = "CONFIRMED"
	StateProcessing TransactionState = "PROCESSING"
	StateFailed     TransactionState = "FAILED"
	StateAuthorized TransactionState = "AUTHORIZED"
	StateCompleted  TransactionState = "COMPLETED"
	StateFulfill    TransactionState = "FULFILL"
	StateDecline    TransactionState = "DECLINE"
	StateVoided     TransactionState = "VOIDED"
)

type WalleeTransaction struct {
	ProviderTransaction

	// acceptHeader contains the header which indicates the language preferences of the buyer.
	AcceptHeader string `json:"acceptHeader"`

	// acceptLanguageHeader contains the header which indicates the language preferences of the buyer.
	AcceptLanguageHeader string `json:"acceptLanguageHeader"`

	// allowedPaymentMethodBrands is a collection of payment method brand IDs.
	AllowedPaymentMethodBrands []int64 `json:"allowedPaymentMethodBrands"`

	// allowedPaymentMethodConfigurations is a collection of payment method configuration IDs.
	AllowedPaymentMethodConfigurations []int64 `json:"allowedPaymentMethodConfigurations"`

	// authorizationAmount is the amount authorized for the transaction.
	AuthorizationAmount float64 `json:"authorizationAmount"`

	// authorizationEnvironment is the environment in which this transaction was successfully authorized.
	AuthorizationEnvironment string `json:"authorizationEnvironment"`

	// authorizationSalesChannel is the sales channel through which the transaction was placed.
	AuthorizationSalesChannel int64 `json:"authorizationSalesChannel"`

	// authorizationTimeoutOn is the time on which the transaction will be timed out when it is not at least authorized.
	AuthorizationTimeoutOn time.Time `json:"authorizationTimeoutOn"`

	// authorizedOn is the timestamp when the transaction was authorized.
	AuthorizedOn time.Time `json:"authorizedOn"`

	// autoConfirmationEnabled indicates whether auto confirmation is enabled for the transaction.
	AutoConfirmationEnabled bool `json:"autoConfirmationEnabled"`

	// billingAddress is the address associated with the transaction.
	BillingAddress string `json:"-"`

	// chargeRetryEnabled indicates whether charging retry is enabled for the transaction.
	ChargeRetryEnabled bool `json:"chargeRetryEnabled"`

	// completedAmount is the total amount which has been captured so far.
	CompletedAmount float64 `json:"completedAmount"`

	// completedOn is the timestamp when the transaction was completed.
	CompletedOn time.Time `json:"completedOn"`

	// completionBehavior controls when the transaction is completed.
	CompletionBehavior string `json:"completionBehavior"`

	// completionTimeoutOn is the timestamp when the transaction completion will time out.
	CompletionTimeoutOn time.Time `json:"completionTimeoutOn"`

	// confirmedBy is the user ID who confirmed the transaction.
	ConfirmedBy int64 `json:"confirmedBy"`

	// confirmedOn is the timestamp when the transaction was confirmed.
	ConfirmedOn time.Time `json:"confirmedOn"`

	// createdBy is the user ID who created the transaction.
	CreatedBy int64 `json:"createdBy"`

	// createdOn is the timestamp when the transaction was created.
	CreatedOn time.Time `json:"createdOn"`

	// currency is the currency code associated with the transaction.
	Currency string `json:"currency"`

	// customerEmailAddress is the email address of the customer.
	CustomerEmailAddress string `json:"customerEmailAddress"`

	// customerId is the ID of the customer associated with the transaction.
	CustomerID string `json:"customerId"`

	// customersPresence indicates what kind of authentication method was used during authorization.
	CustomersPresence string `json:"customersPresence"`

	// deliveryDecisionMadeOn is the timestamp when the decision has been made if a transaction should be delivered or not.
	DeliveryDecisionMadeOn time.Time `json:"deliveryDecisionMadeOn"`

	// deviceSessionIdentifier links the transaction with the session identifier provided in the URL of the device data JavaScript.
	DeviceSessionIdentifier string `json:"deviceSessionIdentifier"`

	// emailsDisabled indicates whether email sending is disabled for this particular transaction.
	EmailsDisabled bool `json:"emailsDisabled"`

	// endOfLife indicates the date from which on no operation can be carried out anymore.
	EndOfLife time.Time `json:"endOfLife"`

	// environment is the environment in which the transaction is processed.
	Environment string `json:"environment"`

	// environmentSelectionStrategy determines how the environment (test or production) for processing the transaction is selected.
	EnvironmentSelectionStrategy string `json:"environmentSelectionStrategy"`

	// failedOn is the timestamp when the transaction failed.
	FailedOn time.Time `json:"failedOn"`

	// failedUrl is the URL to which the user will be redirected when the transaction fails.
	FailedURL string `json:"failedUrl"`

	// failureReason describes why the transaction failed.
	FailureReason string `json:"failureReason"`

	// group is the transaction group associated with the transaction.
	Group string `json:"-"`

	// id is the unique identifier for the transaction.
	ID int64 `json:"id"`

	// internetProtocolAddress identifies the device of the buyer.
	InternetProtocolAddress string `json:"internetProtocolAddress"`

	// internetProtocolAddressCountry is the country associated with the Internet Protocol (IP) address.
	InternetProtocolAddressCountry string `json:"internetProtocolAddressCountry"`

	// invoiceMerchantReference is the merchant reference associated with the invoice.
	InvoiceMerchantReference string `json:"invoiceMerchantReference"`

	// javaEnabled indicates whether Java is enabled for the transaction.
	JavaEnabled bool `json:"javaEnabled"`

	// language is the language linked to the transaction.
	Language string `json:"language"`

	// lineItems is a collection of line items associated with the transaction.
	LineItems []string `json:"-"`

	// linkedSpaceId is the ID of the space this transaction belongs to.
	LinkedSpaceID int64 `json:"linkedSpaceId"`

	// merchantReference is the merchant reference associated with the transaction.
	MerchantReference string `json:"merchantReference"`

	// metaData allows storing additional information about the transaction.
	MetaData map[string]string `json:"metaData"`

	// parent is the parent transaction associated with this transaction.
	Parent int64 `json:"parent"`

	// paymentConnectorConfiguration is the connector configuration associated with the payment.
	PaymentConnectorConfiguration string `json:"-"`

	// plannedPurgeDate is the date when the transaction is planned to be permanently removed.
	PlannedPurgeDate time.Time `json:"plannedPurgeDate"`

	// processingOn is the timestamp when the transaction is being processed.
	ProcessingOn time.Time `json:"processingOn"`

	// refundedAmount is the total amount which has been refunded so far.
	RefundedAmount float64 `json:"refundedAmount"`

	// screenColorDepth is the color depth of the screen associated with the transaction.
	ScreenColorDepth string `json:"screenColorDepth"`

	// screenHeight is the height of the screen associated with the transaction.
	ScreenHeight string `json:"screenHeight"`

	// screenWidth is the width of the screen associated with the transaction.
	ScreenWidth string `json:"screenWidth"`

	// shippingAddress is the address associated with the shipping of the transaction.
	ShippingAddress string `json:"-"`

	// shippingMethod is the method used for shipping in the transaction.
	ShippingMethod string `json:"shippingMethod"`

	// spaceViewId is the ID of the space view associated with the transaction.
	SpaceViewID int64 `json:"spaceViewId"`

	// state is the current state of the transaction.
	State TransactionState `json:"state"`

	// successUrl is the URL to which the user will be redirected when the transaction succeeds.
	SuccessURL string `json:"successUrl"`

	// terminal is the terminal on which the payment was processed.
	Terminal string `json:"-"`

	// timeZone is the time zone in which the customer is located.
	TimeZone string `json:"timeZone"`

	// token is the token associated with the transaction.
	Token string `json:"-"`

	// tokenizationMode controls if and how the tokenization of payment information is applied to the transaction.
	TokenizationMode string `json:"tokenizationMode"`

	// totalAppliedFees is the sum of all fees that have been applied so far.
	TotalAppliedFees float64 `json:"totalAppliedFees"`

	// totalSettledAmount is the total amount which has been settled so far.
	TotalSettledAmount float64 `json:"totalSettledAmount"`

	// userAgentHeader provides the user agent of the buyer.
	UserAgentHeader string `json:"userAgentHeader"`

	// userFailureMessage describes why the transaction failed for the end user.
	UserFailureMessage string `json:"userFailureMessage"`

	// userInterfaceType defines through which user interface the transaction has been processed.
	UserInterfaceType string `json:"userInterfaceType"`

	// version is used for optimistic locking and incremented whenever the object is updated.
	Version int `json:"version"`

	// windowHeight is the height of the window associated with the transaction.
	WindowHeight string `json:"windowHeight"`

	// windowWidth is the width of the window associated with the transaction.
	WindowWidth string `json:"windowWidth"`

	// yearsToKeep is the number of years the transaction will be stored after it has been authorized.
	YearsToKeep int `json:"yearsToKeep"`
}