Skip to content

Commit 2947d04

Browse files
authored
Introduce idempotency_key as post request option (#4)
Introduce idempotency_key support
1 parent 3aaaa96 commit 2947d04

16 files changed

+430
-4
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
- uses: actions/checkout@v2
3535
- uses: ruby/setup-ruby@v1
3636
with:
37-
ruby-version: 3.0
37+
ruby-version: 2.6
3838
bundler-cache: true
3939
- name: Run style checks
4040
run: bundle exec rubocop

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
shift4 (2.0.0)
4+
shift4 (2.1.0)
55
httparty (~> 0.20)
66

77
GEM

lib/shift4.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
require 'shift4/fraud_warnings'
1818
require 'shift4/payment_methods'
1919
require 'shift4/plans'
20+
require 'shift4/request_options'
2021
require 'shift4/subscriptions'
2122
require 'shift4/tokens'
2223

lib/shift4/communicator.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ def self.request(json: nil, query: nil, body: nil, config: Configuration)
3434
"Accept" => "application/json",
3535
}
3636
headers["Shift4-Merchant"] = config.merchant unless config.merchant.nil?
37+
if config.is_a?(RequestOptions) && !config.idempotency_key.nil?
38+
headers["Idempotency-Key"] = config.idempotency_key
39+
end
3740

3841
if json
3942
raise ArgumentError("Cannot specify both body and json") if body

lib/shift4/request_options.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# frozen_string_literal: true
2+
3+
module Shift4
4+
class RequestOptions < Configuration
5+
class << self
6+
attr_reader :idempotency_key
7+
end
8+
9+
def initialize(
10+
config = Configuration,
11+
idempotency_key: nil
12+
)
13+
super(
14+
secret_key: config.secret_key,
15+
merchant: config.merchant,
16+
api_url: config.api_url,
17+
uploads_url: config.uploads_url
18+
)
19+
@idempotency_key = idempotency_key
20+
end
21+
22+
attr_reader :idempotency_key
23+
end
24+
end

lib/shift4/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# frozen_string_literal: true
22

33
module Shift4
4-
VERSION = '2.0.0'
4+
VERSION = '2.1.0'
55
end

spec/integration/blacklist_spec.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,29 @@
1616
expect(retrieved['email']).to eq(email)
1717
end
1818

19+
it 'create only one rule on blacklist with idempotency_key' do
20+
# given
21+
email = random_email
22+
request = { ruleType: 'email', email: email, }
23+
24+
request_options = Shift4::RequestOptions.new(idempotency_key: random_idempotency_key.to_s)
25+
26+
created = Shift4::Blacklist.create(
27+
request,
28+
request_options
29+
)
30+
31+
# when
32+
not_created_because_idempotency = Shift4::Blacklist.create(
33+
request,
34+
request_options
35+
)
36+
37+
# then
38+
expect(created['id']).to eq(not_created_because_idempotency['id'])
39+
expect(not_created_because_idempotency.headers['Idempotent-Replayed']).to eq("true")
40+
end
41+
1942
it 'delete blacklist' do
2043
# given
2144
email = random_email

spec/integration/cards_spec.rb

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,39 @@
2727
expect(retrieved['customerId']).to eq(customer_id)
2828
end
2929

30+
it 'create only one card with idempotency_key' do
31+
# given
32+
customer = Shift4::Customers.create(TestData.customer)
33+
customer_id = customer['id']
34+
cardholder_name = random_string
35+
request_options = Shift4::RequestOptions.new(idempotency_key: random_idempotency_key.to_s)
36+
37+
created = Shift4::Cards.create(customer_id,
38+
{
39+
number: '4242424242424242',
40+
expMonth: '12',
41+
expYear: '2055',
42+
cvc: '123',
43+
cardholderName: cardholder_name
44+
},
45+
request_options)
46+
47+
# when
48+
not_created_because_idempotency = Shift4::Cards.create(customer_id,
49+
{
50+
number: '4242424242424242',
51+
expMonth: '12',
52+
expYear: '2055',
53+
cvc: '123',
54+
cardholderName: cardholder_name
55+
},
56+
request_options)
57+
58+
# then
59+
expect(created['id']).to eq(not_created_because_idempotency['id'])
60+
expect(not_created_because_idempotency.headers['Idempotent-Replayed']).to eq("true")
61+
end
62+
3063
it 'update card' do
3164
# given
3265
customer = Shift4::Customers.create(TestData.customer)
@@ -56,6 +89,47 @@
5689
expect(updated_card['addressLine2']).to eq('updated addressLine2')
5790
end
5891

92+
it 'update card only once with idempotency_key' do
93+
# given
94+
customer = Shift4::Customers.create(TestData.customer)
95+
card = Shift4::Cards.create(customer['id'], TestData.card)
96+
97+
request_options = Shift4::RequestOptions.new(idempotency_key: random_idempotency_key.to_s)
98+
99+
# when
100+
Shift4::Cards.update(customer['id'],
101+
card['id'],
102+
{
103+
expMonth: '05',
104+
expYear: '55',
105+
cardholderName: 'updated cardholderName',
106+
addressCountry: 'updated addressCountry',
107+
addressCity: 'updated addressCity',
108+
addressState: 'updated addressState',
109+
addressZip: 'updated addressZip',
110+
addressLine1: 'updated addressLine1',
111+
addressLine2: 'updated addressLine2'
112+
},
113+
request_options)
114+
not_updated_because_idempotency = Shift4::Cards.update(customer['id'],
115+
card['id'],
116+
{
117+
expMonth: '05',
118+
expYear: '55',
119+
cardholderName: 'updated cardholderName',
120+
addressCountry: 'updated addressCountry',
121+
addressCity: 'updated addressCity',
122+
addressState: 'updated addressState',
123+
addressZip: 'updated addressZip',
124+
addressLine1: 'updated addressLine1',
125+
addressLine2: 'updated addressLine2'
126+
},
127+
request_options)
128+
129+
# then
130+
expect(not_updated_because_idempotency.headers['Idempotent-Replayed']).to eq("true")
131+
end
132+
59133
it 'delete card' do
60134
# given
61135
customer = Shift4::Customers.create(TestData.customer)

spec/integration/charges_spec.rb

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,27 @@
3939
expect(retrieved['card']['first4']).to eq(charge_req["card"]["first4"])
4040
end
4141

42+
it 'create charge only once with idempotency_key' do
43+
# given
44+
charge_req = TestData.charge(card: TestData.card)
45+
request_options = Shift4::RequestOptions.new(idempotency_key: random_idempotency_key.to_s)
46+
47+
# when
48+
created = Shift4::Charges.create(charge_req, request_options)
49+
not_created_because_idempotency = Shift4::Charges.create(charge_req, request_options)
50+
51+
# then
52+
expect(created['id']).to eq(not_created_because_idempotency['id'])
53+
expect(not_created_because_idempotency.headers['Idempotent-Replayed']).to eq("true")
54+
end
55+
4256
it 'update charge' do
4357
# given
4458
card = TestData.card
4559
charge_req = TestData.charge(card: card)
60+
created = Shift4::Charges.create(charge_req)
4661

4762
# when
48-
created = Shift4::Charges.create(charge_req)
4963
updated = Shift4::Charges.update(created['id'],
5064
"description" => "updated description",
5165
"metadata" => { "key" => "updated value" })
@@ -62,6 +76,33 @@
6276
expect(updated['card']['first4']).to eq(charge_req["card"]["first4"])
6377
end
6478

79+
it 'update charge only once with idempotency_key' do
80+
# given
81+
card = TestData.card
82+
charge_req = TestData.charge(card: card)
83+
created = Shift4::Charges.create(charge_req)
84+
85+
request_options = Shift4::RequestOptions.new(idempotency_key: random_idempotency_key.to_s)
86+
87+
# when
88+
Shift4::Charges.update(created['id'],
89+
{
90+
"description" => "updated description",
91+
"metadata" => { "key" => "updated value" }
92+
},
93+
request_options)
94+
95+
not_updated_because_idempotency = Shift4::Charges.update(created['id'],
96+
{
97+
"description" => "updated description",
98+
"metadata" => { "key" => "updated value" }
99+
},
100+
request_options)
101+
102+
# then
103+
expect(not_updated_because_idempotency.headers['Idempotent-Replayed']).to eq("true")
104+
end
105+
65106
it 'capture charge' do
66107
# given
67108
charge_req = TestData.charge(card: TestData.card, captured: false)
@@ -75,6 +116,22 @@
75116
expect(captured['captured']).to eq(true)
76117
end
77118

119+
it 'capture charge only once with idempotency_key' do
120+
# given
121+
charge_req = TestData.charge(card: TestData.card, captured: false)
122+
created = Shift4::Charges.create(charge_req)
123+
124+
request_options = Shift4::RequestOptions.new(idempotency_key: random_idempotency_key.to_s)
125+
126+
# when
127+
captured = Shift4::Charges.capture(created['id'], request_options)
128+
not_captured_because_idempotency = Shift4::Charges.capture(created['id'], request_options)
129+
130+
# then
131+
expect(captured['id']).to eq(not_captured_because_idempotency['id'])
132+
expect(not_captured_because_idempotency.headers['Idempotent-Replayed']).to eq("true")
133+
end
134+
78135
it 'refund charge' do
79136
# given
80137
charge_req = TestData.charge(card: TestData.card, captured: false)
@@ -88,6 +145,22 @@
88145
expect(refunded['refunded']).to eq(true)
89146
end
90147

148+
it 'refund charge only once with idempotency_key' do
149+
# given
150+
charge_req = TestData.charge(card: TestData.card, captured: false)
151+
created = Shift4::Charges.create(charge_req)
152+
153+
request_options = Shift4::RequestOptions.new(idempotency_key: random_idempotency_key.to_s)
154+
155+
# when
156+
refunded = Shift4::Charges.refund(created['id'], nil, request_options)
157+
not_refunded_because_idempotency = Shift4::Charges.refund(created['id'], nil, request_options)
158+
159+
# then
160+
expect(refunded['id']).to eq(not_refunded_because_idempotency['id'])
161+
expect(not_refunded_because_idempotency.headers['Idempotent-Replayed']).to eq("true")
162+
end
163+
91164
it 'list charges' do
92165
# given
93166
customer = Shift4::Customers.create(TestData.customer)

spec/integration/credits_spec.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,20 @@
1919
expect(retrieved['card']['first4']).to eq(credit_req["card"]["first4"])
2020
end
2121

22+
it 'create only once with idempotency_key' do
23+
# given
24+
credit_req = TestData.credit(card: TestData.card)
25+
request_options = Shift4::RequestOptions.new(idempotency_key: random_idempotency_key.to_s)
26+
27+
# when
28+
created = Shift4::Credits.create(credit_req, request_options)
29+
not_created_because_idempotency = Shift4::Credits.create(credit_req, request_options)
30+
31+
# then
32+
expect(created['id']).to eq(not_created_because_idempotency['id'])
33+
expect(not_created_because_idempotency.headers['Idempotent-Replayed']).to eq("true")
34+
end
35+
2236
it 'update credit' do
2337
# given
2438
card = TestData.card
@@ -42,6 +56,32 @@
4256
expect(updated['card']['first4']).to eq(credit_req["card"]["first4"])
4357
end
4458

59+
it 'update credit only once with idempotency_key' do
60+
# given
61+
card = TestData.card
62+
credit_req = TestData.credit(card: card)
63+
created = Shift4::Credits.create(credit_req)
64+
65+
request_options = Shift4::RequestOptions.new(idempotency_key: random_idempotency_key.to_s)
66+
67+
# when
68+
Shift4::Credits.update(created['id'],
69+
{
70+
"description" => "updated description",
71+
"metadata" => { "key" => "updated value" }
72+
},
73+
request_options)
74+
not_updated_because_idempotency = Shift4::Credits.update(created['id'],
75+
{
76+
"description" => "updated description",
77+
"metadata" => { "key" => "updated value" }
78+
},
79+
request_options)
80+
81+
# then
82+
expect(not_updated_because_idempotency.headers['Idempotent-Replayed']).to eq("true")
83+
end
84+
4585
it 'list credits' do
4686
# given
4787
customer = Shift4::Customers.create(TestData.customer)

spec/integration/customers_spec.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,20 @@
1717
expect(retrieved['email']).to eq(customer_req['email'])
1818
end
1919

20+
it 'create only once with idempotency_key' do
21+
# given
22+
customer_req = TestData.customer
23+
request_options = Shift4::RequestOptions.new(idempotency_key: random_idempotency_key.to_s)
24+
25+
# when
26+
created = Shift4::Customers.create(customer_req, request_options)
27+
not_created_because_idempotency = Shift4::Customers.create(customer_req, request_options)
28+
29+
# then
30+
expect(created['id']).to eq(not_created_because_idempotency['id'])
31+
expect(not_created_because_idempotency.headers['Idempotent-Replayed']).to eq("true")
32+
end
33+
2034
it 'update customer default card' do
2135
# given
2236
customer_req = TestData.customer(card: TestData.card)
@@ -32,6 +46,26 @@
3246
expect(updated['defaultCardId']).to eq(new_card['id'])
3347
end
3448

49+
it 'update customer only once with idempotency_key' do
50+
# given
51+
customer_req = TestData.customer(card: TestData.card)
52+
customer = Shift4::Customers.create(customer_req)
53+
new_card = Shift4::Cards.create(customer['id'], TestData.card)
54+
55+
request_options = Shift4::RequestOptions.new(idempotency_key: random_idempotency_key.to_s)
56+
57+
# when
58+
Shift4::Customers.update(customer['id'],
59+
{ defaultCardId: new_card['id'] },
60+
request_options)
61+
not_updated_because_idempotency = Shift4::Customers.update(customer['id'],
62+
{ defaultCardId: new_card['id'] },
63+
request_options)
64+
65+
# then
66+
expect(not_updated_because_idempotency.headers['Idempotent-Replayed']).to eq("true")
67+
end
68+
3569
it 'delete customer' do
3670
# given
3771
customer_req = TestData.customer(card: TestData.card)

0 commit comments

Comments
 (0)