Skip to content

Commit 9763eb9

Browse files
authored
Merge pull request #166 from Adyen/feature/terminal-api
Feature/terminal api
2 parents 22b68b9 + 6f77d50 commit 9763eb9

File tree

10 files changed

+332
-0
lines changed

10 files changed

+332
-0
lines changed

README.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ This library supports the following:
2121
| [Recurring API](https://docs.adyen.com/api-explorer/Recurring/68/overview) | v68 | Endpoints for managing saved payment details. | [Recurring](lib/adyen/services/recurring.rb) |
2222
| [Stored Value API](https://docs.adyen.com/payment-methods/gift-cards/stored-value-api) | v46 | Manage both online and point-of-sale gift cards and other stored-value cards. | [StoredValue](lib/adyen/services/storedValue.rb) |
2323
| [Transfers API](https://docs.adyen.com/api-explorer/transfers/3/overview) | v3 | The Transfers API provides endpoints that can be used to get information about all your transactions, move funds within your balance platform or send funds from your balance platform to a transfer instrument. | [Transfers](lib/adyen/services/transfers.rb) |
24+
| [Cloud-based Terminal API](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/terminal-api-reference/) | - | Our point-of-sale integration. | [TerminalCloudAPI](lib/adyen/services/terminalCloudAPI.rb) |
2425

2526
For more information, refer to our [documentation](https://docs.adyen.com/) or the [API Explorer](https://docs.adyen.com/api-explorer/).
2627

@@ -99,6 +100,121 @@ To run the tests use :
99100
bundle install --with development
100101
~~~~
101102

103+
## Using the Cloud Terminal API Integration
104+
In order to submit In-Person requests with [Terminal API over Cloud](https://docs.adyen.com/point-of-sale/design-your-integration/choose-your-architecture/cloud/) you need to initialize the client in the same way as explained above for Ecommerce transactions:
105+
``` ruby
106+
# Step 1: Require the parts of the module you want to use
107+
require 'adyen-ruby-api-library'
108+
109+
# Step 2: Initialize the client object
110+
adyen = Adyen::Client.new(api_key: 'YOUR_API_KEY', env: :test)
111+
112+
# Step 3: Create the request
113+
serviceID = "123456789"
114+
saleID = "POS-SystemID12345"
115+
POIID = "Your Device Name(eg V400m-123456789)"
116+
117+
# Use a unique transaction for every transaction you perform
118+
transactionID = "TransactionID"
119+
120+
request =
121+
{
122+
"SaleToPOIRequest": {
123+
"MessageHeader": {
124+
"MessageClass": "Service",
125+
"MessageCategory": "Payment",
126+
"MessageType": "Request",
127+
"ServiceID": serviceID,
128+
"SaleID": saleID,
129+
"POIID": POIID,
130+
"ProtocolVersion": "3.0"
131+
},
132+
"PaymentRequest": {
133+
"SaleData": {
134+
"SaleTransactionID": {
135+
"TransactionID": transactionID,
136+
"TimeStamp": "2023-08-23T09:48:55"
137+
},
138+
"SaleToAcquirerData": "eyJhcHBsaWNhdGlvbkluZm8iOnsiYWR5ZW5MaWJyYXJ5Ijp7Im5hbWUiOiJhZ....",
139+
"TokenRequestedType": "Transaction"
140+
},
141+
"PaymentTransaction": {
142+
"AmountsReq": {
143+
"Currency": "EUR",
144+
"RequestedAmount": 10
145+
}
146+
}
147+
}
148+
}
149+
}
150+
151+
# Step 4: Make the request
152+
response = adyen.terminal_cloud_api.sync(request)
153+
```
154+
155+
### Optional: perform an abort request
156+
157+
To perform an [abort request](https://docs.adyen.com/point-of-sale/basic-tapi-integration/cancel-a-transaction/) you can use the following example:
158+
``` ruby
159+
abortRequest =
160+
{
161+
"MessageHeader": {
162+
"MessageClass": "Service",
163+
"MessageCategory": "Abort",
164+
"MessageType": "Request",
165+
"ServiceID": serviceID,
166+
"SaleID": saleID,
167+
"POIID": POIID,
168+
"ProtocolVersion": "3.0"
169+
},
170+
"AbortRequest": {
171+
"AbortReason": "MerchantAbort",
172+
"MessageReference": {
173+
"MessageCategory": "Payment",
174+
"SaleID": saleID,
175+
# Service ID of the payment you're aborting
176+
"ServiceID": serviceID,
177+
"POIID": POIID
178+
}
179+
}
180+
}
181+
182+
response = adyen.terminal_cloud_api.sync(abortRequest)
183+
```
184+
185+
### Optional: perform a status request
186+
187+
To perform a [status request](https://docs.adyen.com/point-of-sale/basic-tapi-integration/verify-transaction-status/) you can use the following example:
188+
``` ruby
189+
statusRequest =
190+
{
191+
"MessageHeader": {
192+
"MessageClass": "Service",
193+
"MessageCategory": "TransactionStatus",
194+
"MessageType": "Request",
195+
"ServiceID": serviceID,
196+
"SaleID": saleID,
197+
"POIID": POIID,
198+
"ProtocolVersion": "3.0"
199+
},
200+
"TransactionStatusRequest": {
201+
"ReceiptReprintFlag": true,
202+
"DocumentQualifier": [
203+
"CashierReceipt",
204+
"CustomerReceipt"
205+
],
206+
"MessageReference": {
207+
"SaleID": saleID,
208+
# serviceID of the transaction you want the status update for
209+
"ServiceID": serviceID,
210+
"MessageCategory": "Payment"
211+
}
212+
}
213+
}
214+
215+
response = adyen.terminal_cloud_api.sync(statusRequest)
216+
```
217+
102218
## Feedback
103219
We value your input! Help us enhance our API Libraries and improve the integration experience by providing your feedback. Please take a moment to fill out [our feedback form](https://forms.gle/A4EERrR6CWgKWe5r9) to share your thoughts, suggestions or ideas.
104220

lib/adyen-ruby-api-library.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@
1919
require_relative 'adyen/services/management'
2020
require_relative 'adyen/services/storedValue'
2121
require_relative 'adyen/services/balanceControlService'
22+
require_relative 'adyen/services/terminalCloudAPI'

lib/adyen/client.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ def service_url_base(service)
7575
when 'Management'
7676
url = "https://management-#{@env}.adyen.com"
7777
supports_live_url_prefix = false
78+
when 'TerminalCloudAPI'
79+
url = "https://terminal-api-#{@env}.adyen.com"
80+
supports_live_url_prefix = false
7881
else
7982
raise ArgumentError, 'Invalid service specified'
8083
end
@@ -98,6 +101,8 @@ def service_url_base(service)
98101
def service_url(service, action, version)
99102
if service == "Checkout" && @env == :live
100103
return "#{service_url_base(service)}/checkout/v#{version}/#{action}"
104+
elsif version == nil
105+
return "#{service_url_base(service)}/#{action}"
101106
else
102107
return "#{service_url_base(service)}/v#{version}/#{action}"
103108
end
@@ -221,6 +226,9 @@ def call_adyen_api(service, action, request_data, headers, version, _with_applic
221226
# delete has no response.body (unless it throws an error)
222227
if response.body.nil? || response.body === ''
223228
AdyenResult.new('{}', response.headers, response.status)
229+
# terminal API async call returns always 'ok'
230+
elsif response.body === 'ok'
231+
AdyenResult.new('{}', response.headers, response.status)
224232
else
225233
AdyenResult.new(response.body, response.headers, response.status)
226234
end
@@ -286,6 +294,10 @@ def stored_value
286294
def balance_control_service
287295
@balance_control_service ||= Adyen::BalanceControlService.new(self)
288296
end
297+
298+
def terminal_cloud_api
299+
@terminal_cloud_api ||= Adyen::TerminalCloudAPI.new(self)
300+
end
289301
end
290302
end
291303
# rubocop:enable all
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
require_relative './service'
2+
module Adyen
3+
class TerminalCloudAPI < Service
4+
attr_accessor :service
5+
6+
def initialize(client)
7+
super(client, nil ,'TerminalCloudAPI')
8+
end
9+
10+
def connected_terminals(request, headers: {})
11+
endpoint = '/connectedTerminals'.gsub(/{.+?}/, '%s')
12+
endpoint = endpoint.gsub(%r{^/}, '')
13+
endpoint = format(endpoint)
14+
15+
action = { method: 'post', url: endpoint }
16+
@client.call_adyen_api(@service, action, request, headers, @version)
17+
end
18+
19+
def sync(request, headers: {})
20+
endpoint = '/sync'.gsub(/{.+?}/, '%s')
21+
endpoint = endpoint.gsub(%r{^/}, '')
22+
endpoint = format(endpoint)
23+
24+
action = { method: 'post', url: endpoint }
25+
@client.call_adyen_api(@service, action, request, headers, @version)
26+
end
27+
28+
def async(request, headers: {})
29+
endpoint = '/async'.gsub(/{.+?}/, '%s')
30+
endpoint = endpoint.gsub(%r{^/}, '')
31+
endpoint = format(endpoint)
32+
33+
action = { method: 'post', url: endpoint }
34+
@client.call_adyen_api(@service, action, request, headers, @version)
35+
end
36+
37+
end
38+
end

spec/client_spec.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,4 +236,23 @@
236236
expect(client.service_url('PosTerminalManagement', 'assignTerminals', '1'))
237237
.to eq('https://postfmapi-test.adyen.com/postfmapi/terminal/v1/assignTerminals')
238238
end
239+
240+
it 'checks the creation of TerminalCloudAPI sync url' do
241+
client = Adyen::Client.new(api_key: 'api_key', env: :test)
242+
expect(client.service_url('TerminalCloudAPI', 'sync', nil))
243+
.to eq('https://terminal-api-test.adyen.com/sync')
244+
end
245+
246+
it 'checks the creation of TerminalCloudAPI async url' do
247+
client = Adyen::Client.new(api_key: 'api_key', env: :test)
248+
expect(client.service_url('TerminalCloudAPI', 'async', nil))
249+
.to eq('https://terminal-api-test.adyen.com/async')
250+
end
251+
252+
it 'checks the creation of TerminalCloudAPI connectedTerminals url' do
253+
client = Adyen::Client.new(api_key: 'api_key', env: :test)
254+
expect(client.service_url('TerminalCloudAPI', 'connectedTerminals', nil))
255+
.to eq('https://terminal-api-test.adyen.com/connectedTerminals')
256+
257+
end
239258
end
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"merchantAccount": "YourMerchantAccount"
3+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"SaleToPOIRequest": {
3+
"MessageHeader": {
4+
"MessageClass": "Service",
5+
"MessageCategory": "Payment",
6+
"MessageType": "Request",
7+
"ServiceID": "1233094855",
8+
"SaleID": "POSSystemID12345",
9+
"POIID": "V400m-1234123412",
10+
"ProtocolVersion": "3.0"
11+
},
12+
"PaymentRequest": {
13+
"SaleData": {
14+
"SaleTransactionID": {
15+
"TransactionID": "123456764",
16+
"TimeStamp": "2023-08-23T09:48:55"
17+
},
18+
"SaleToAcquirerData": "eyJhcHBsaWNhdGlvbkluZm8iOnsiYWR5ZW5MaWJyYXJ5Ijp7Im5hbWUiOiJhZ....",
19+
"TokenRequestedType": "Transaction"
20+
},
21+
"PaymentTransaction": {
22+
"AmountsReq": {
23+
"Currency": "EUR",
24+
"RequestedAmount": 10
25+
}
26+
}
27+
}
28+
}
29+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"uniqueTerminalIds": [
3+
"V400m-1234123412"
4+
]
5+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"SaleToPOIResponse":{
3+
"MessageHeader":{},
4+
"PaymentResponse":{
5+
"POIData":{
6+
"POITransactionID":{
7+
"TransactionID": "oLkO0012498220087000.KHQC5N7G84BLNK43"
8+
}
9+
},
10+
"Response":{
11+
"Result":"Success",
12+
"AdditionalResponse":"...shopperEmail=shoppersemail%40address.com..."
13+
}
14+
},
15+
"PaymentReceipt":{}
16+
}
17+
}
18+

spec/terminal_cloud_api_spec.rb

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
require 'spec_helper'
2+
require 'json'
3+
4+
RSpec.describe Adyen::TerminalCloudAPI, service: 'TerminalCloudAPI' do
5+
before(:all) do
6+
@shared_values = {
7+
client: create_client(:api_key),
8+
service: 'TerminalCloudAPI'
9+
}
10+
end
11+
12+
it 'makes a connectedTerminals POST call' do
13+
request_body = JSON.parse(json_from_file('mocks/requests/TerminalCloudAPI/connected_terminals.json'))
14+
15+
response_body = json_from_file('mocks/responses/TerminalCloudAPI/connected_terminals.json')
16+
17+
url = @shared_values[:client].service_url(@shared_values[:service], 'connectedTerminals', nil)
18+
WebMock.stub_request(:post, url)
19+
.with(
20+
headers: {
21+
'x-api-key' => @shared_values[:client].api_key
22+
}
23+
)
24+
.to_return(
25+
body: response_body
26+
)
27+
28+
result = @shared_values[:client].terminal_cloud_api.connected_terminals(request_body)
29+
response_hash = result.response
30+
31+
expect(result.status)
32+
.to eq(200)
33+
expect(response_hash)
34+
.to eq(JSON.parse(response_body))
35+
expect(response_hash)
36+
.to be_a Adyen::HashWithAccessors
37+
expect(response_hash)
38+
.to be_a_kind_of Hash
39+
end
40+
41+
it 'makes a sync payment POST call' do
42+
request_body = JSON.parse(json_from_file('mocks/requests/TerminalCloudAPI/sync_payment.json'))
43+
44+
response_body = json_from_file('mocks/responses/TerminalCloudAPI/sync_payment.json')
45+
46+
url = @shared_values[:client].service_url(@shared_values[:service], 'sync', nil)
47+
WebMock.stub_request(:post, url)
48+
.with(
49+
headers: {
50+
'x-api-key' => @shared_values[:client].api_key
51+
}
52+
)
53+
.to_return(
54+
body: response_body
55+
)
56+
57+
result = @shared_values[:client].terminal_cloud_api.sync(request_body)
58+
response_hash = result.response
59+
60+
expect(result.status)
61+
.to eq(200)
62+
expect(response_hash)
63+
.to eq(JSON.parse(response_body))
64+
expect(response_hash)
65+
.to be_a Adyen::HashWithAccessors
66+
expect(response_hash)
67+
.to be_a_kind_of Hash
68+
end
69+
70+
it 'makes an async payment POST call' do
71+
request_body = JSON.parse(json_from_file('mocks/requests/TerminalCloudAPI/sync_payment.json'))
72+
73+
url = @shared_values[:client].service_url(@shared_values[:service], 'async', nil)
74+
WebMock.stub_request(:post, url)
75+
.with(
76+
headers: {
77+
'x-api-key' => @shared_values[:client].api_key
78+
}
79+
)
80+
.to_return(
81+
body: 'ok'
82+
)
83+
84+
result = @shared_values[:client].terminal_cloud_api.async(request_body)
85+
response_hash = result.response
86+
87+
expect(result.status)
88+
.to eq(200)
89+
end
90+
end
91+
# rubocop:enable Metrics/BlockLength

0 commit comments

Comments
 (0)