afterpay-sdk 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.env.sample +2 -0
- data/.github/workflows/gempush.yml +30 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.rubocop.yml +33 -0
- data/.travis.yml +10 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +89 -0
- data/LICENSE.txt +21 -0
- data/README.md +284 -0
- data/Rakefile +6 -0
- data/afterpay-sdk.gemspec +45 -0
- data/bin/bundle +105 -0
- data/bin/console +20 -0
- data/bin/setup +8 -0
- data/configure.rb +19 -0
- data/lib/afterpay-sdk.rb +6 -0
- data/lib/afterpay.rb +48 -0
- data/lib/afterpay/address.rb +49 -0
- data/lib/afterpay/client.rb +72 -0
- data/lib/afterpay/config.rb +23 -0
- data/lib/afterpay/consumer.rb +35 -0
- data/lib/afterpay/discount.rb +28 -0
- data/lib/afterpay/error.rb +16 -0
- data/lib/afterpay/item.rb +54 -0
- data/lib/afterpay/order.rb +117 -0
- data/lib/afterpay/payment.rb +164 -0
- data/lib/afterpay/payment_event.rb +16 -0
- data/lib/afterpay/refund.rb +31 -0
- data/lib/afterpay/shipping_courier.rb +14 -0
- data/lib/afterpay/utils/money.rb +26 -0
- data/lib/afterpay/version.rb +5 -0
- metadata +215 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Afterpay
|
4
|
+
class Discount
|
5
|
+
attr_accessor :name, :amount
|
6
|
+
|
7
|
+
def initialize(name:, amount:)
|
8
|
+
@name = name
|
9
|
+
@amount = amount
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_hash
|
13
|
+
{
|
14
|
+
displayName: name,
|
15
|
+
amount: Utils::Money.api_hash(amount)
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.from_response(response)
|
20
|
+
return nil if response.nil?
|
21
|
+
|
22
|
+
new(
|
23
|
+
name: response[:display_name],
|
24
|
+
amount: Utils::Money.from_response(response[:amount])
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Afterpay
|
4
|
+
# Error class with accessor to methods
|
5
|
+
# Afterpay error returns the same format containing
|
6
|
+
# `errorId`, `errorCode`, `message`
|
7
|
+
class Error
|
8
|
+
attr_accessor :code, :id, :message
|
9
|
+
|
10
|
+
def initialize(response)
|
11
|
+
@id = response[:errorId]
|
12
|
+
@code = response[:errorCode]
|
13
|
+
@message = response[:message]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "money"
|
4
|
+
|
5
|
+
module Afterpay
|
6
|
+
class Item
|
7
|
+
attr_accessor :name, :sku, :quantity, :page_url, :image_url, :price, :categories, :estimated_shipment_date
|
8
|
+
|
9
|
+
# rubocop:disable Metrics/ParameterLists
|
10
|
+
def initialize(name:, price:, page_url:, image_url:, categories:, estimated_shipment_date:, sku: nil, quantity: 1)
|
11
|
+
@name = name
|
12
|
+
@sku = sku
|
13
|
+
@quantity = quantity
|
14
|
+
@price = price
|
15
|
+
@page_url = page_url
|
16
|
+
@image_url = image_url
|
17
|
+
@categories = categories
|
18
|
+
@estimated_shipment_date = estimated_shipment_date
|
19
|
+
end
|
20
|
+
# rubocop:enable Metrics/ParameterLists
|
21
|
+
|
22
|
+
def to_hash
|
23
|
+
{
|
24
|
+
name: name,
|
25
|
+
sku: sku,
|
26
|
+
quantity: quantity,
|
27
|
+
price: {
|
28
|
+
amount: price.amount.to_f,
|
29
|
+
currency: price.currency.iso_code
|
30
|
+
},
|
31
|
+
page_url: page_url,
|
32
|
+
image_url: image_url,
|
33
|
+
categories: categories,
|
34
|
+
estimated_shipment_date: estimated_shipment_date
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Builds Item from response
|
39
|
+
def self.from_response(response)
|
40
|
+
return nil if response.nil?
|
41
|
+
|
42
|
+
new(
|
43
|
+
name: response[:name],
|
44
|
+
sku: response[:sku],
|
45
|
+
quantity: response[:quantity],
|
46
|
+
price: Utils::Money.from_response(response[:price]),
|
47
|
+
page_url: response[:pageUrl],
|
48
|
+
image_url: response[:imageUrl],
|
49
|
+
categories: response[:categories],
|
50
|
+
estimated_shipment_date: response[:estimatedShipmentDate]
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Afterpay
|
4
|
+
# The Order object for creating an order to `/v2/checkouts`
|
5
|
+
class Order
|
6
|
+
attr_accessor :total, :consumer, :items, :shipping, :tax, :discounts,
|
7
|
+
:billing, :shipping_address, :billing_address, :reference,
|
8
|
+
:payment_type, :success_url, :cancel_url, :redirect_checkout_url
|
9
|
+
|
10
|
+
attr_reader :expiry, :token, :error
|
11
|
+
|
12
|
+
# Finds Order from Afterpay API
|
13
|
+
# @param token [String]
|
14
|
+
# @return [Order]
|
15
|
+
def self.find(token)
|
16
|
+
request = Afterpay.client.get("/v2/checkouts/#{token}")
|
17
|
+
|
18
|
+
Order.from_response(request.body)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Builds Order from response
|
22
|
+
# @param response [Hash] response params from API
|
23
|
+
# @return [Order]
|
24
|
+
def self.from_response(response)
|
25
|
+
return nil if response.nil?
|
26
|
+
|
27
|
+
new(
|
28
|
+
total: Utils::Money.from_response(response[:total]),
|
29
|
+
consumer: Consumer.from_response(response[:consumer]),
|
30
|
+
items: response[:items].map { |item| Item.from_response(item) },
|
31
|
+
billing_address: Address.from_response(response[:billing]),
|
32
|
+
shipping_address: Address.from_response(response[:shipping]),
|
33
|
+
discounts: response[:discounts].map { |discount| Discount.from_response(discount) },
|
34
|
+
tax: Utils::Money.from_response(response[:taxAmount]),
|
35
|
+
shipping: Utils::Money.from_response(response[:shippingAmount])
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Helper function to create an Order and calls #create
|
40
|
+
#
|
41
|
+
# @param (see #initialize)
|
42
|
+
# @return [Order::Response] containing token, error, valid?
|
43
|
+
def self.create(*args)
|
44
|
+
new(*args).create
|
45
|
+
end
|
46
|
+
|
47
|
+
# Initializes an Order object
|
48
|
+
#
|
49
|
+
# @overload initialize(total:, items:, consumer:, success_url:, cancel_url:, payment_type:)
|
50
|
+
# @param total [Money] a Money object
|
51
|
+
# @param items [Array<Afterpay::Item>] receives items for order
|
52
|
+
# @param consumer [Afterpay::Consume] the consumer for the order
|
53
|
+
# @param success_url [String] the path to redirect on successful payment
|
54
|
+
# @param cancel_url [String] the path to redirect on failed payment
|
55
|
+
# @param payment_type [String] Payment type defaults to {Config#type}
|
56
|
+
# @param shipping [Money] optional the billing Address
|
57
|
+
# @param discounts [Array<Afterpay::Discount>] optional discounts
|
58
|
+
# @param billing_address [<Afterpay::Address>] optional the billing Address
|
59
|
+
# @param shipping_address [<Afterpay::Address>] optional the shipping Address
|
60
|
+
def initialize(attributes = {})
|
61
|
+
attributes.each { |key, value| instance_variable_set(:"@#{key}", value) }
|
62
|
+
@apayment_type ||= Afterpay.config.type
|
63
|
+
@token ||= nil
|
64
|
+
@expiry ||= nil
|
65
|
+
@error = nil
|
66
|
+
end
|
67
|
+
|
68
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
69
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
70
|
+
|
71
|
+
# Builds structure to API specs
|
72
|
+
def to_hash
|
73
|
+
data = {
|
74
|
+
amount: Utils::Money.api_hash(total),
|
75
|
+
consumer: consumer.to_hash,
|
76
|
+
merchant: {
|
77
|
+
redirectConfirmUrl: success_url,
|
78
|
+
redirectCancelUrl: cancel_url
|
79
|
+
},
|
80
|
+
merchantReference: reference,
|
81
|
+
taxAmount: tax,
|
82
|
+
paymentType: payment_type
|
83
|
+
}
|
84
|
+
data[items] = items.map(&:to_hash) if items
|
85
|
+
data[:taxAmount] = Utils::Money.api_hash(tax) if tax
|
86
|
+
data[:shippingAmount] = Utils::Money.api_hash(shipping) if shipping
|
87
|
+
data[:discounts] = discounts.map(&:to_hash) if discounts
|
88
|
+
data[:billing] = billing_address.to_hash if billing_address
|
89
|
+
data[:shipping] = shipping_address.to_hash if shipping_address
|
90
|
+
data
|
91
|
+
end
|
92
|
+
|
93
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
94
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
95
|
+
|
96
|
+
# Sends the create request to Afterpay server
|
97
|
+
# @return [Response]
|
98
|
+
def create
|
99
|
+
request = Afterpay.client.post("/v2/checkouts") do |req|
|
100
|
+
req.body = to_hash
|
101
|
+
end
|
102
|
+
response = request.body
|
103
|
+
if request.success?
|
104
|
+
@expiry = response[:expires]
|
105
|
+
@token = response[:token]
|
106
|
+
@redirect_checkout_url = response[:redirectCheckoutUrl]
|
107
|
+
else
|
108
|
+
@error = Error.new(response)
|
109
|
+
end
|
110
|
+
self
|
111
|
+
end
|
112
|
+
|
113
|
+
def success?
|
114
|
+
!@token.nil?
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Metrics/ClassLength
|
4
|
+
|
5
|
+
module Afterpay
|
6
|
+
# They Payment object
|
7
|
+
class Payment
|
8
|
+
attr_accessor :id, :token, :status, :created, :original_amount, :open_to_capture_amount,
|
9
|
+
:payment_state, :merchant_reference, :refunds, :order, :events, :error
|
10
|
+
|
11
|
+
# Initialize Payment from response
|
12
|
+
def initialize(attributes)
|
13
|
+
@id = attributes[:id].to_i
|
14
|
+
@token = attributes[:token]
|
15
|
+
@status = attributes[:status]
|
16
|
+
@created = attributes[:created]
|
17
|
+
@original_amount = Utils::Money.from_response(attributes[:originalAmount])
|
18
|
+
@open_to_capture_amount = Utils::Money.from_response(attributes[:openToCaptureAmount])
|
19
|
+
@payment_state = attributes[:paymentState]
|
20
|
+
@merchant_reference = attributes[:merchantReference]
|
21
|
+
@refunds = attributes[:refunds]
|
22
|
+
@order = Order.from_response(attributes[:orderDetails])
|
23
|
+
@events = attributes[:events]
|
24
|
+
@error = Error.new(attributes) if attributes[:errorId]
|
25
|
+
end
|
26
|
+
|
27
|
+
def success?
|
28
|
+
@status == "APPROVED"
|
29
|
+
end
|
30
|
+
|
31
|
+
# Executes the Payment
|
32
|
+
#
|
33
|
+
# @param token [String] the Order token
|
34
|
+
# @param reference [String] the reference for payment
|
35
|
+
# @return [Payment] the Payment object
|
36
|
+
def self.execute(token:, reference:)
|
37
|
+
request = Afterpay.client.post("/v2/payments/capture") do |req|
|
38
|
+
req.body = {
|
39
|
+
token: token,
|
40
|
+
merchantRefernce: reference
|
41
|
+
}
|
42
|
+
end
|
43
|
+
new(request.body)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.execute_auth(request_id:, token:, merchant_reference:)
|
47
|
+
request = Afterpay.client.post("/v2/payments/auth") do |req|
|
48
|
+
req.body = {
|
49
|
+
requestId: request_id,
|
50
|
+
token: token,
|
51
|
+
merchantReference: merchant_reference
|
52
|
+
}
|
53
|
+
end
|
54
|
+
new(request.body)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.execute_deferred_payment(request_id:, reference:, amount:,
|
58
|
+
payment_event_merchant_reference:, order_id:)
|
59
|
+
request = Afterpay.client.post("/v2/payments/#{order_id}/capture") do |req|
|
60
|
+
req.body = {
|
61
|
+
requestId: request_id,
|
62
|
+
merchantRefernce: reference,
|
63
|
+
amount: Utils::Money.api_hash(amount),
|
64
|
+
paymentEventMerchantReference: payment_event_merchant_reference
|
65
|
+
}
|
66
|
+
end
|
67
|
+
new(request.body)
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.execute_void(request_id:, order_id:, amount:)
|
71
|
+
request = Afterpay.client.post("/v2/payments/#{order_id}/void") do |req|
|
72
|
+
req.body = {
|
73
|
+
requestId: request_id,
|
74
|
+
amount: Utils::Money.api_hash(amount)
|
75
|
+
}
|
76
|
+
end
|
77
|
+
new(request.body)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.update_shipping_courier(order_id:, shipped_at:, name:, tracking:, priority:)
|
81
|
+
request = Afterpay.client.put("/v2/payments/#{order_id}/courier") do |req|
|
82
|
+
req.body = {
|
83
|
+
shippedAt: shipped_at,
|
84
|
+
name: name,
|
85
|
+
tracking: tracking,
|
86
|
+
priority: priority
|
87
|
+
}
|
88
|
+
end
|
89
|
+
new(request.body)
|
90
|
+
end
|
91
|
+
|
92
|
+
# This endpoint retrieves an individual payment along with its order details.
|
93
|
+
def self.get_payment_by_order_id(order_id:)
|
94
|
+
request = Afterpay.client.get("/v2/payments/#{order_id}")
|
95
|
+
new(request.body)
|
96
|
+
end
|
97
|
+
|
98
|
+
# This endpoint retrieves an individual payment along with its order details.
|
99
|
+
def self.get_payment_by_token(token:)
|
100
|
+
request = Afterpay.client.get("/v2/payments/token:#{token}")
|
101
|
+
new(request.body)
|
102
|
+
end
|
103
|
+
|
104
|
+
# This end point is for merchants that creates merchant side's order id after
|
105
|
+
# AfterPay order id creation. The merchants should call immediately after the
|
106
|
+
# AfterPay order is created in order to properly update with their order id
|
107
|
+
# that can be tracked.
|
108
|
+
def self.update_payment_by_order_id(order_id:, merchant_reference:)
|
109
|
+
request = Afterpay.client.put("/v2/payments/#{order_id}") do |req|
|
110
|
+
req.body = {
|
111
|
+
# The merchant's new order id to replace with
|
112
|
+
merchantReference: merchant_reference
|
113
|
+
}
|
114
|
+
end
|
115
|
+
request.body
|
116
|
+
end
|
117
|
+
|
118
|
+
# This endpoint performs a reversal of the checkout that is used to initiate
|
119
|
+
# the Afterpay payment process. This will cancel the order asynchronously as
|
120
|
+
# soon as it is created without the need of an additional call to the void endpoint.
|
121
|
+
# In order for a payment to be eligible, the order must be in an Auth-Approved or
|
122
|
+
# Captured state and must be issued within 10 minutes of the order being created.
|
123
|
+
# token paramater is the token of the checkout to be reversed (voided).
|
124
|
+
def self.reverse_payment_by_token(token:)
|
125
|
+
request = Afterpay.client.post("/v2/payments/token:#{token}/reversal") do |req|
|
126
|
+
req.body = {}
|
127
|
+
end
|
128
|
+
request.status
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.create_url(url, type, values)
|
132
|
+
if values.size.positive?
|
133
|
+
values.each do |value|
|
134
|
+
url += "&#{type}=#{value}"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
url
|
138
|
+
end
|
139
|
+
|
140
|
+
# rubocop:disable Metrics/ParameterLists
|
141
|
+
|
142
|
+
# This endpoint retrieves a collection of payments along with their order details.
|
143
|
+
def self.list_payments(to_created_date:, from_created_date:, limit:, offset:, tokens:, ids:,
|
144
|
+
merchant_ref:, statuses:, order_by:, asc:)
|
145
|
+
url = "/v2/payments?"
|
146
|
+
url += "toCreatedDate=#{to_created_date.gsub('+', '%2b')}" if to_created_date
|
147
|
+
url += "&fromCreatedDate=#{from_created_date.gsub('+', '%2b')}" if from_created_date
|
148
|
+
url += "&limit=#{limit}" if limit
|
149
|
+
url += "&offset=#{offset}" if offset
|
150
|
+
url += "&orderBy=#{order_by}" if order_by
|
151
|
+
url += "&ascending=#{asc}" if asc
|
152
|
+
url += create_url(url, "ids", ids)
|
153
|
+
url += create_url(url, "tokens", tokens)
|
154
|
+
url += create_url(url, "merchantReferences", merchant_ref)
|
155
|
+
url += create_url(url, "statuses", statuses)
|
156
|
+
request = Afterpay.client.get(url)
|
157
|
+
request.body
|
158
|
+
end
|
159
|
+
|
160
|
+
# rubocop:enable Metrics/ParameterLists
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# rubocop:enable Metrics/ClassLength
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Afterpay
|
4
|
+
class PaymentEvent
|
5
|
+
attr_accessor :id, :created, :expires, :type, :amount, :payment_event_merchant_reference
|
6
|
+
|
7
|
+
def initialize(attributes)
|
8
|
+
@id = attributes[:id]
|
9
|
+
@created = attributes[:created]
|
10
|
+
@expires = attributes[:expires]
|
11
|
+
@type = attributes[:expires]
|
12
|
+
@amount = Utils::Money.from_response(attributes[:amount])
|
13
|
+
@payment_event_merchant_reference = attributes[:paymentEventMerchantReference]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Afterpay
|
4
|
+
class Refund
|
5
|
+
attr_accessor :request_id, :amount, :merchant_reference, :refund_id, :refunded_at,
|
6
|
+
:refund_merchant_reference, :error
|
7
|
+
|
8
|
+
def initialize(attributes)
|
9
|
+
@request_id = attributes[:requestId]
|
10
|
+
@amount = attributes[:amount]
|
11
|
+
@merchant_reference = attributes[:merchantReference]
|
12
|
+
@refund_id = attributes[:refundId]
|
13
|
+
@refunded_at = attributes[:refundAt]
|
14
|
+
@refund_merchant_reference = attributes[:refundMerchantReference]
|
15
|
+
@error = Error.new(attributes) if attributes[:errorId]
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.execute(request_id:, order_id:, amount:, merchant_reference:,
|
19
|
+
refund_merchant_reference:)
|
20
|
+
request = Afterpay.client.post("/v2/payments/#{order_id}/refund") do |req|
|
21
|
+
req.body = {
|
22
|
+
requestId: request_id,
|
23
|
+
amount: Utils::Money.api_hash(amount),
|
24
|
+
merchantReference: merchant_reference,
|
25
|
+
refundMerchantReference: refund_merchant_reference
|
26
|
+
}
|
27
|
+
end
|
28
|
+
new(request.body)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Afterpay
|
4
|
+
class ShippingCourier
|
5
|
+
attr_accessor :shipped_at, :name, :tracking, :priority
|
6
|
+
|
7
|
+
def initialize(shipped_at:, name:, tracking:, priority:)
|
8
|
+
@shipped_at = shipped_at
|
9
|
+
@name = name
|
10
|
+
@tracking = tracking
|
11
|
+
@priority = priority
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|