cashless2ecash

cashless2ecash: pay with cards for digital cash (experimental)
Log | Files | Refs | README

commit f9398c5e2fa24da5c670d3d2e41176a8f9b038c0
parent db698ebc8ec77c554628004da02f9eb57cb2b7be
Author: Joel-Haeberli <haebu@rubigen.ch>
Date:   Sun, 12 May 2024 16:43:27 +0200

attestation: fix request

Diffstat:
Mc2ec/http-util.go | 18++++++++++++++----
Mc2ec/wallee-client.go | 40+++++++++++++++++++++++++++++++++-------
Ac2ec/wallee-client_test.go | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mc2ec/wallee-models.go | 334++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
4 files changed, 414 insertions(+), 81 deletions(-)

diff --git a/c2ec/http-util.go b/c2ec/http-util.go @@ -148,7 +148,6 @@ func HttpGet[T any]( } if res.StatusCode > 299 { LogInfo("http-util", fmt.Sprintf("response: %s", string(b))) - fmt.Println(fmt.Sprintf("response: %s", string(b))) return nil, res.StatusCode, nil } resBody, err := codec.Decode(bytes.NewReader(b)) @@ -171,8 +170,6 @@ func HttpPost[T any, R any]( return nil, -1, err } - fmt.Println(string(bodyEncoded)) - req, err := http.NewRequest(HTTP_POST, url, bytes.NewBuffer(bodyEncoded)) if err != nil { return nil, -1, err @@ -182,6 +179,7 @@ func HttpPost[T any, R any]( req.Header.Add(k, v) } req.Header.Add("Accept", resCodec.HttpApplicationContentHeader()) + req.Header.Add("Content-Type", reqCodec.HttpApplicationContentHeader()) res, err := http.DefaultClient.Do(req) if err != nil { @@ -191,7 +189,19 @@ func HttpPost[T any, R any]( if resCodec == nil { return nil, res.StatusCode, err } else { - resBody, err := resCodec.Decode(res.Body) + b, err := io.ReadAll(res.Body) + if err != nil { + LogError("http-util", err) + if res.StatusCode > 299 { + return nil, res.StatusCode, nil + } + return nil, -1, err + } + if res.StatusCode > 299 { + LogInfo("http-util", fmt.Sprintf("response: %s", string(b))) + return nil, res.StatusCode, nil + } + resBody, err := resCodec.Decode(bytes.NewReader(b)) return resBody, res.StatusCode, err } } diff --git a/c2ec/wallee-client.go b/c2ec/wallee-client.go @@ -21,6 +21,7 @@ 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_SEARCH_TRANSACTION_API = "/api/transaction/search" const WALLEE_CREATE_REFUND_API = "/api/refund/refund" const WALLEE_API_SPACEID_PARAM_NAME = "spaceId" @@ -55,7 +56,7 @@ func (wt *WalleeTransaction) AbortWithdrawal() bool { func (wt *WalleeTransaction) FormatPayto() string { - return fmt.Sprintf("payto://wallee-transaction/%d", wt.ID) + return fmt.Sprintf("payto://wallee-transaction/%d", wt.Id) } func (wt *WalleeTransaction) Bytes() []byte { @@ -96,26 +97,52 @@ func (w *WalleeClient) SetupClient(p *Provider) error { func (w *WalleeClient) GetTransaction(transactionId string) (ProviderTransaction, error) { - call := WALLEE_READ_TRANSACTION_API + call := WALLEE_SEARCH_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 := prepareWalleeHeaders(url, HTTP_GET, w.credentials.UserId, w.credentials.ApplicationUserKey) + hdrs, err := prepareWalleeHeaders(url, HTTP_POST, w.credentials.UserId, w.credentials.ApplicationUserKey) if err != nil { return nil, err } - t, status, err := HttpGet(url, hdrs, NewJsonCodec[WalleeTransaction]()) + filter := WalleeSearchFilter{ + FieldName: "merchantReference", + Operator: EQUALS, + Type: LEAF, + Value: transactionId, + } + + req := WalleeTransactionSearchRequest{ + Filter: filter, + Language: "en", + NumberOfEntities: 1, + StartingEntity: 0, + } + + t, status, err := HttpPost( + url, + hdrs, + &req, + NewJsonCodec[WalleeTransactionSearchRequest](), + NewJsonCodec[[]*WalleeTransaction](), + ) if err != nil { return nil, err } if status != HTTP_OK { return nil, errors.New("no result") } - return t, nil + if t == nil { + return nil, errors.New("no such transaction") + } + derefRes := *t + if len(derefRes) < 1 { + return nil, errors.New("no such transaction") + } + return derefRes[0], nil } func (sc *WalleeClient) FormatPayto(w *Withdrawal) string { @@ -246,7 +273,6 @@ func calculateWalleeAuthToken( } LogInfo("wallee-client", fmt.Sprintf("authMsg (utf-8 encoded): %s", string(authMsg))) - fmt.Println("wallee-client", fmt.Sprintf("authMsg (utf-8 encoded): %s", string(authMsg))) key := make([]byte, 32) _, err := base64.StdEncoding.Decode(key, []byte(userKeyBase64)) diff --git a/c2ec/wallee-client_test.go b/c2ec/wallee-client_test.go @@ -0,0 +1,103 @@ +package main + +import ( + "errors" + "fmt" + "strconv" + "strings" + "testing" +) + +const TEST_SPACE_ID = 0 +const TEST_USER_ID = 0 +const TEST_ACCESS_TOKEN = "" +const TEST_TRANSACTION_ID = 0 + +func TestCutSchemeAndHost(t *testing.T) { + + urls := []string{ + "https://app-wallee.com/api/transaction/search?spaceId=54275", + "https://app-wallee.com/api/transaction/search?spaceId=54275?spaceId=54275&id=212156032", + "/api/transaction/search?spaceId=54275?spaceId=54275&id=212156032", + "http://test.com.ag.ch.de-en/api/transaction/search?spaceId=54275?spaceId=54275&id=212156032", + } + + for _, url := range urls { + cutted := cutSchemeAndHost(url) + fmt.Println(cutted) + if !strings.HasPrefix(cutted, "/api") { + t.FailNow() + } + } +} + +func TestWalleeMac(t *testing.T) { + + // https://app-wallee.com/en-us/doc/api/web-service#_java + // assuming the java example on the website of wallee is correct + // the following parameters should result to the given expected + // result using my Golang implementation. + + // authStr := "1|100000|1715454671|GET|/api/transaction/read?spaceId=10000&id=200000000" + secret := "OWOMg2gnaSx1nukAM6SN2vxedfY1yLPONvcTKbhDv7I=" + expected := "PNqpGIkv+4jVcdIYqp5Pp2tKGWSjO1bNdEAIPgllWb7A6BDRvQQ/I2fnZF20roAIJrP22pe1LvHH8lWpIzJbWg==" + + calculated, err := calculateWalleeAuthToken(100000, int64(1715454671), "GET", "https://some.domain/api/transaction/read?spaceId=10000&id=200000000", secret) + if err != nil { + t.Error(err) + t.FailNow() + } + + fmt.Println("expected:", expected) + fmt.Println("calcultd:", calculated) + + if expected != calculated { + t.Error(errors.New("calculated auth token not equal to expected token")) + t.FailNow() + } +} + +func TestTransactionSearchIntegration(t *testing.T) { + + filter := WalleeSearchFilter{ + FieldName: "merchantReference", + Operator: EQUALS, + Type: LEAF, + Value: "TTZQFA2QQ14AARC82F7Z2Q9JCH40ZHXCE3BMXJV1FG87BP2GA3P0", + } + + req := WalleeTransactionSearchRequest{ + Filter: filter, + Language: "en", + NumberOfEntities: 1, + StartingEntity: 0, + } + + api := "https://app-wallee.com/api/transaction/search" + api = FormatUrl(api, map[string]string{}, map[string]string{"spaceId": strconv.Itoa(TEST_SPACE_ID)}) + + hdrs, err := prepareWalleeHeaders(api, "POST", TEST_USER_ID, TEST_ACCESS_TOKEN) + if err != nil { + fmt.Println("Error preparing headers (req1): ", err.Error()) + t.FailNow() + } + + for k, v := range hdrs { + fmt.Println("req1", k, v) + } + + p, s, err := HttpPost( + api, + hdrs, + &req, + NewJsonCodec[WalleeTransactionSearchRequest](), + NewJsonCodec[[]*WalleeTransaction](), + ) + if err != nil { + fmt.Println("Error executing request: ", err.Error()) + fmt.Println("Status: ", s) + } else { + fmt.Println("wallee response status: ", s) + fmt.Println("wallee response: ", p) + } +} diff --git a/c2ec/wallee-models.go b/c2ec/wallee-models.go @@ -4,6 +4,32 @@ import ( "time" ) +type WalleeSearchOperator string + +type WalleeSearchType string + +const ( + LEAF WalleeSearchType = "LEAF" +) + +const ( + EQUALS WalleeSearchOperator = "EQUALS" +) + +type WalleeSearchFilter struct { + FieldName string `json:"fieldName"` + Operator WalleeSearchOperator `json:"operator"` + Type WalleeSearchType `json:"type"` + Value string `json:"value"` +} + +type WalleeTransactionSearchRequest struct { + Filter WalleeSearchFilter `json:"filter"` + Language string `json:"language"` + NumberOfEntities int `json:"numberOfEntities"` + StartingEntity int `json:"startingEntity"` +} + type WalleeTransactionCompletion struct { Amount float64 `json:"amount"` BaseLineItems []WalleeLineItem `json:"baseLineItems"` @@ -124,74 +150,242 @@ const ( type WalleeTransaction struct { ProviderTransaction - AcceptHeader string `json:"acceptHeader"` - AcceptLanguageHeader string `json:"acceptLanguageHeader"` - AllowedPaymentMethodBrands []int64 `json:"allowedPaymentMethodBrands"` - AllowedPaymentMethodConfigurations []int64 `json:"allowedPaymentMethodConfigurations"` - AuthorizationAmount float64 `json:"authorizationAmount"` - AuthorizationEnvironment string `json:"authorizationEnvironment"` - AuthorizationSalesChannel int64 `json:"authorizationSalesChannel"` - AuthorizationTimeoutOn time.Time `json:"authorizationTimeoutOn"` - AuthorizedOn time.Time `json:"authorizedOn"` - AutoConfirmationEnabled bool `json:"autoConfirmationEnabled"` - BillingAddress string `json:"-"` - ChargeRetryEnabled bool `json:"chargeRetryEnabled"` - CompletedAmount float64 `json:"completedAmount"` - CompletedOn time.Time `json:"completedOn"` - CompletionBehavior string `json:"completionBehavior"` - CompletionTimeoutOn time.Time `json:"completionTimeoutOn"` - ConfirmedBy int64 `json:"confirmedBy"` - ConfirmedOn time.Time `json:"confirmedOn"` - CreatedBy int64 `json:"createdBy"` - CreatedOn time.Time `json:"createdOn"` - Currency string `json:"currency"` - CustomerEmailAddress string `json:"customerEmailAddress"` - CustomerID string `json:"customerId"` - CustomersPresence string `json:"customersPresence"` - DeliveryDecisionMadeOn time.Time `json:"deliveryDecisionMadeOn"` - DeviceSessionIdentifier string `json:"deviceSessionIdentifier"` - EmailsDisabled bool `json:"emailsDisabled"` - EndOfLife time.Time `json:"endOfLife"` - Environment string `json:"environment"` - EnvironmentSelectionStrategy string `json:"environmentSelectionStrategy"` - FailedOn time.Time `json:"failedOn"` - FailedURL string `json:"failedUrl"` - FailureReason string `json:"failureReason"` - Group string `json:"-"` - ID int64 `json:"id"` - InternetProtocolAddress string `json:"internetProtocolAddress"` - InternetProtocolAddressCountry string `json:"internetProtocolAddressCountry"` - InvoiceMerchantReference string `json:"invoiceMerchantReference"` - JavaEnabled bool `json:"javaEnabled"` - Language string `json:"language"` - LineItems []string `json:"-"` - LinkedSpaceID int64 `json:"linkedSpaceId"` - MerchantReference string `json:"merchantReference"` - MetaData map[string]string `json:"metaData"` - Parent int64 `json:"parent"` - PaymentConnectorConfiguration string `json:"-"` - PlannedPurgeDate time.Time `json:"plannedPurgeDate"` - ProcessingOn time.Time `json:"processingOn"` - RefundedAmount float64 `json:"refundedAmount"` - ScreenColorDepth string `json:"screenColorDepth"` - ScreenHeight string `json:"screenHeight"` - ScreenWidth string `json:"screenWidth"` - ShippingAddress string `json:"-"` - ShippingMethod string `json:"shippingMethod"` - SpaceViewID int64 `json:"spaceViewId"` - State WalleeTransactionState `json:"state"` - SuccessURL string `json:"successUrl"` - Terminal string `json:"-"` - TimeZone string `json:"timeZone"` - Token string `json:"-"` - TokenizationMode string `json:"tokenizationMode"` - TotalAppliedFees float64 `json:"totalAppliedFees"` - TotalSettledAmount float64 `json:"totalSettledAmount"` - UserAgentHeader string `json:"userAgentHeader"` - UserFailureMessage string `json:"userFailureMessage"` - UserInterfaceType string `json:"userInterfaceType"` - Version int `json:"version"` - WindowHeight string `json:"windowHeight"` - WindowWidth string `json:"windowWidth"` - YearsToKeep int `json:"yearsToKeep"` + AcceptHeader interface{} `json:"acceptHeader"` + AcceptLanguageHeader interface{} `json:"acceptLanguageHeader"` + AllowedPaymentMethodBrands []interface{} `json:"allowedPaymentMethodBrands"` + AllowedPaymentMethodConfigurations []interface{} `json:"allowedPaymentMethodConfigurations"` + AuthorizationAmount float64 `json:"authorizationAmount"` + AuthorizationEnvironment string `json:"authorizationEnvironment"` + AuthorizationSalesChannel int64 `json:"authorizationSalesChannel"` + AuthorizationTimeoutOn time.Time `json:"authorizationTimeoutOn"` + AuthorizedOn time.Time `json:"authorizedOn"` + AutoConfirmationEnabled bool `json:"autoConfirmationEnabled"` + BillingAddress interface{} `json:"billingAddress"` + ChargeRetryEnabled bool `json:"chargeRetryEnabled"` + CompletedAmount float64 `json:"completedAmount"` + CompletedOn interface{} `json:"completedOn"` + CompletionBehavior string `json:"completionBehavior"` + CompletionTimeoutOn interface{} `json:"completionTimeoutOn"` + ConfirmedBy int `json:"confirmedBy"` + ConfirmedOn time.Time `json:"confirmedOn"` + CreatedBy int `json:"createdBy"` + CreatedOn time.Time `json:"createdOn"` + Currency string `json:"currency"` + CustomerEmailAddress interface{} `json:"customerEmailAddress"` + CustomerId interface{} `json:"customerId"` + CustomersPresence string `json:"customersPresence"` + DeliveryDecisionMadeOn interface{} `json:"deliveryDecisionMadeOn"` + DeviceSessionIdentifier interface{} `json:"deviceSessionIdentifier"` + EmailsDisabled bool `json:"emailsDisabled"` + EndOfLife time.Time `json:"endOfLife"` + Environment string `json:"environment"` + EnvironmentSelectionStrategy string `json:"environmentSelectionStrategy"` + FailedOn interface{} `json:"failedOn"` + FailedUrl interface{} `json:"failedUrl"` + FailureReason interface{} `json:"failureReason"` + Group WalleeGroup `json:"group"` + Id int `json:"id"` + InternetProtocolAddress interface{} `json:"internetProtocolAddress"` + InternetProtocolAddressCountry interface{} `json:"internetProtocolAddressCountry"` + InvoiceMerchantReference string `json:"invoiceMerchantReference"` + JavaEnabled interface{} `json:"javaEnabled"` + Language string `json:"language"` + LineItems []WalleeLineItem `json:"lineItems"` + LinkedSpaceId int `json:"linkedSpaceId"` + MerchantReference string `json:"merchantReference"` + MetaData struct{} `json:"metaData"` + Parent interface{} `json:"parent"` + PaymentConnectorConfiguration WalleePaymentConnectorConfiguration `json:"paymentConnectorConfiguration"` + PlannedPurgeDate time.Time `json:"plannedPurgeDate"` + ProcessingOn time.Time `json:"processingOn"` + RefundedAmount float64 `json:"refundedAmount"` + ScreenColorDepth interface{} `json:"screenColorDepth"` + ScreenHeight interface{} `json:"screenHeight"` + ScreenWidth interface{} `json:"screenWidth"` + ShippingAddress interface{} `json:"shippingAddress"` + ShippingMethod interface{} `json:"shippingMethod"` + SpaceViewId interface{} `json:"spaceViewId"` + State string `json:"state"` + SuccessUrl interface{} `json:"successUrl"` + Terminal WalleeTerminal `json:"terminal"` + TimeZone interface{} `json:"timeZone"` + Token interface{} `json:"token"` + TokenizationMode interface{} `json:"tokenizationMode"` + TotalAppliedFees float64 `json:"totalAppliedFees"` + TotalSettledAmount float64 `json:"totalSettledAmount"` + UserAgentHeader interface{} `json:"userAgentHeader"` + UserFailureMessage interface{} `json:"userFailureMessage"` + UserInterfaceType string `json:"userInterfaceType"` + Version int `json:"version"` + WindowHeight interface{} `json:"windowHeight"` + WindowWidth interface{} `json:"windowWidth"` + YearsToKeep int `json:"yearsToKeep"` +} + +type WalleeGroup struct { + BeginDate time.Time `json:"beginDate"` + CustomerId interface{} `json:"customerId"` + EndDate time.Time `json:"endDate"` + Id int `json:"id"` + LinkedSpaceId int `json:"linkedSpaceId"` + PlannedPurgeDate time.Time `json:"plannedPurgeDate"` + State string `json:"state"` + Version int `json:"version"` +} + +type WalleePaymentConnectorConfiguration struct { + ApplicableForTransactionProcessing bool `json:"applicableForTransactionProcessing"` + Conditions []interface{} `json:"conditions"` + Connector int64 `json:"connector"` + EnabledSalesChannels []WalleeEnabledSalesChannels `json:"enabledSalesChannels"` + EnabledSpaceViews []interface{} `json:"enabledSpaceViews"` + Id int `json:"id"` + ImagePath string `json:"imagePath"` + LinkedSpaceId int `json:"linkedSpaceId"` + Name string `json:"name"` + PaymentMethodConfiguration WalleePaymentMethodConfiguration `json:"paymentMethodConfiguration"` + PlannedPurgeDate interface{} `json:"plannedPurgeDate"` + Priority int `json:"priority"` + ProcessorConfiguration WalleeProcessorConfiguration `json:"processorConfiguration"` + State string `json:"state"` + Version int `json:"version"` +} + +type WalleeEnabledSalesChannels struct { + Description WalleeMultilangProperty `json:"description"` + Icon string `json:"icon"` + Id int64 `json:"id"` + Name WalleeMultilangProperty `json:"name"` + SortOrder int `json:"sortOrder"` +} + +type WalleeMultilangProperty struct { + DeDE string `json:"de-DE"` + EnUS string `json:"en-US"` + FrFR string `json:"fr-FR"` + ItIT string `json:"it-IT"` +} + +type WalleePaymentMethodConfiguration struct { + DataCollectionType string `json:"dataCollectionType"` + Description struct{} `json:"description"` + Id int `json:"id"` + ImageResourcePath interface{} `json:"imageResourcePath"` + LinkedSpaceId int `json:"linkedSpaceId"` + Name string `json:"name"` + OneClickPaymentMode string `json:"oneClickPaymentMode"` + PaymentMethod int64 `json:"paymentMethod"` + PlannedPurgeDate interface{} `json:"plannedPurgeDate"` + ResolvedDescription WalleeMultilangProperty `json:"resolvedDescription"` + ResolvedImageUrl string `json:"resolvedImageUrl"` + ResolvedTitle WalleeMultilangProperty `json:"resolvedTitle"` + SortOrder int `json:"sortOrder"` + SpaceId int `json:"spaceId"` + State string `json:"state"` + Title struct{} `json:"title"` + Version int `json:"version"` +} + +type WalleeProcessorConfiguration struct { + ApplicationManaged bool `json:"applicationManaged"` + ContractId interface{} `json:"contractId"` + Id int `json:"id"` + LinkedSpaceId int `json:"linkedSpaceId"` + Name string `json:"name"` + PlannedPurgeDate interface{} `json:"plannedPurgeDate"` + Processor int64 `json:"processor"` + State string `json:"state"` + Version int `json:"version"` +} + +type WalleeTerminal struct { + ConfigurationVersion WalleeConfigurationVersion `json:"configurationVersion"` + DefaultCurrency string `json:"defaultCurrency"` + DeviceName interface{} `json:"deviceName"` + DeviceSerialNumber string `json:"deviceSerialNumber"` + ExternalId string `json:"externalId"` + Id int `json:"id"` + Identifier string `json:"identifier"` + LinkedSpaceId int `json:"linkedSpaceId"` + LocationVersion WalleeLocationVersion `json:"locationVersion"` + Name string `json:"name"` + PlannedPurgeDate interface{} `json:"plannedPurgeDate"` + State string `json:"state"` + Type WalleeType `json:"type"` + Version int `json:"version"` +} + +type WalleeConfigurationVersion struct { + Configuration WalleeConfiguration `json:"configuration"` + ConnectorConfigurations []int `json:"connectorConfigurations"` + CreatedBy int `json:"createdBy"` + CreatedOn time.Time `json:"createdOn"` + DefaultCurrency interface{} `json:"defaultCurrency"` + Id int `json:"id"` + LinkedSpaceId int `json:"linkedSpaceId"` + MaintenanceWindowDuration string `json:"maintenanceWindowDuration"` + MaintenanceWindowStart string `json:"maintenanceWindowStart"` + PlannedPurgeDate interface{} `json:"plannedPurgeDate"` + State string `json:"state"` + TimeZone string `json:"timeZone"` + Version int `json:"version"` + VersionAppliedImmediately bool `json:"versionAppliedImmediately"` +} + +type WalleeConfiguration struct { + Id int `json:"id"` + LinkedSpaceId int `json:"linkedSpaceId"` + Name string `json:"name"` + PlannedPurgeDate interface{} `json:"plannedPurgeDate"` + State string `json:"state"` + Type WalleeType `json:"type"` + Version int `json:"version"` +} + +type WalleeType struct { + Description WalleeMultilangProperty `json:"description"` + Id int64 `json:"id"` + Name WalleeMultilangProperty `json:"name"` +} + +type WalleeLocationVersion struct { + Address WalleeAddress `json:"address"` + ContactAddress interface{} `json:"contactAddress"` + CreatedBy int `json:"createdBy"` + CreatedOn time.Time `json:"createdOn"` + Id int `json:"id"` + LinkedSpaceId int `json:"linkedSpaceId"` + Location WalleeLocation `json:"location"` + PlannedPurgeDate interface{} `json:"plannedPurgeDate"` + State string `json:"state"` + Version int `json:"version"` + VersionAppliedImmediately bool `json:"versionAppliedImmediately"` +} + +type WalleeAddress struct { + City string `json:"city"` + Country string `json:"country"` + DependentLocality string `json:"dependentLocality"` + EmailAddress string `json:"emailAddress"` + FamilyName string `json:"familyName"` + GivenName string `json:"givenName"` + MobilePhoneNumber string `json:"mobilePhoneNumber"` + OrganizationName string `json:"organizationName"` + PhoneNumber string `json:"phoneNumber"` + PostalState interface{} `json:"postalState"` + Postcode string `json:"postcode"` + PostCode string `json:"postCode"` + Salutation string `json:"salutation"` + SortingCode string `json:"sortingCode"` + Street string `json:"street"` +} + +type WalleeLocation struct { + ExternalId string `json:"externalId"` + Id int `json:"id"` + LinkedSpaceId int `json:"linkedSpaceId"` + Name string `json:"name"` + PlannedPurgeDate interface{} `json:"plannedPurgeDate"` + State string `json:"state"` + Version int `json:"version"` }