swoosh 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rubocop.yml +51 -0
- data/.tool-versions +1 -0
- data/AGENTS.md +13 -0
- data/CHANGELOG.md +68 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +21 -0
- data/README.md +377 -0
- data/Rakefile +32 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/certs/Swish_Merchant_TestCertificate_1234679304.csr +27 -0
- data/certs/Swish_Merchant_TestCertificate_1234679304.key +52 -0
- data/certs/Swish_Merchant_TestCertificate_1234679304.p12 +0 -0
- data/certs/Swish_Merchant_TestCertificate_1234679304.pem +98 -0
- data/certs/Swish_Merchant_TestSigningCertificate_1234679304.csr +27 -0
- data/certs/Swish_Merchant_TestSigningCertificate_1234679304.key +52 -0
- data/certs/Swish_Merchant_TestSigningCertificate_1234679304.p12 +0 -0
- data/certs/Swish_Merchant_TestSigningCertificate_1234679304.pem +98 -0
- data/certs/Swish_TLS_RootCA.pem +22 -0
- data/certs/Swish_TechnicalSupplier_TestCertificate_9870474641.csr +27 -0
- data/certs/Swish_TechnicalSupplier_TestCertificate_9870474641.key +52 -0
- data/certs/Swish_TechnicalSupplier_TestCertificate_9870474641.p12 +0 -0
- data/certs/Swish_TechnicalSupplier_TestCertificate_9870474641.pem +98 -0
- data/lib/swoosh/callback/controller.rb +31 -0
- data/lib/swoosh/callback.rb +35 -0
- data/lib/swoosh/certificates.rb +92 -0
- data/lib/swoosh/configuration.rb +54 -0
- data/lib/swoosh/errors.rb +100 -0
- data/lib/swoosh/payment.rb +101 -0
- data/lib/swoosh/payment_request.rb +53 -0
- data/lib/swoosh/qr_code.rb +36 -0
- data/lib/swoosh/railtie.rb +41 -0
- data/lib/swoosh/test.rb +116 -0
- data/lib/swoosh/token_store.rb +66 -0
- data/lib/swoosh/version.rb +5 -0
- data/lib/swoosh.rb +172 -0
- metadata +86 -0
data/lib/swoosh.rb
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "openssl"
|
|
6
|
+
require "securerandom"
|
|
7
|
+
require "uri"
|
|
8
|
+
require_relative "swoosh/version"
|
|
9
|
+
require_relative "swoosh/errors"
|
|
10
|
+
require_relative "swoosh/configuration"
|
|
11
|
+
require_relative "swoosh/certificates"
|
|
12
|
+
require_relative "swoosh/payment"
|
|
13
|
+
require_relative "swoosh/payment_request"
|
|
14
|
+
require_relative "swoosh/token_store"
|
|
15
|
+
require_relative "swoosh/qr_code"
|
|
16
|
+
require_relative "swoosh/callback"
|
|
17
|
+
require_relative "swoosh/railtie" if defined? Rails::Railtie
|
|
18
|
+
|
|
19
|
+
module Swoosh
|
|
20
|
+
class << self
|
|
21
|
+
attr_writer :client
|
|
22
|
+
|
|
23
|
+
def configuration
|
|
24
|
+
@configuration ||= Configuration.new
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Swoosh.configure { |c| c.environment = :production }
|
|
28
|
+
def configure
|
|
29
|
+
yield configuration
|
|
30
|
+
@client = nil
|
|
31
|
+
configuration
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def client
|
|
35
|
+
@client ||= Main.new(configuration: configuration)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def reset!
|
|
39
|
+
@configuration = nil
|
|
40
|
+
@client = nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def generate_payment(amount, **options)
|
|
44
|
+
client.generate_payment(amount, **options)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def find_payment(id)
|
|
48
|
+
client.find_payment(id)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def cancel_payment(id)
|
|
52
|
+
client.cancel_payment(id)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def token_for(payment_id)
|
|
56
|
+
client.token_for(payment_id)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class Main
|
|
61
|
+
TEST_URL = "https://mss.cpc.getswish.net/swish-cpcapi/api/v2/paymentrequests"
|
|
62
|
+
PRODUCTION_URL = "https://cpc.getswish.net/swish-cpcapi/api/v2/paymentrequests"
|
|
63
|
+
|
|
64
|
+
# Creating a payment is v2 (we supply the id); reading one back is v1.
|
|
65
|
+
TEST_LOOKUP_URL = "https://mss.cpc.getswish.net/swish-cpcapi/api/v1/paymentrequests"
|
|
66
|
+
PRODUCTION_LOOKUP_URL = "https://cpc.getswish.net/swish-cpcapi/api/v1/paymentrequests"
|
|
67
|
+
|
|
68
|
+
JSON_CONTENT_TYPE = "application/json"
|
|
69
|
+
|
|
70
|
+
# Cancelling is a JSON Patch against the payment's status, not a body of its
|
|
71
|
+
# own, and Swish rejects it with a 415 under any other content type.
|
|
72
|
+
JSON_PATCH_CONTENT_TYPE = "application/json-patch+json"
|
|
73
|
+
CANCEL_PATCH = [{ op: "replace", path: "/status", value: "cancelled" }].freeze
|
|
74
|
+
|
|
75
|
+
REQUEST_CLASSES = {
|
|
76
|
+
get: Net::HTTP::Get,
|
|
77
|
+
put: Net::HTTP::Put,
|
|
78
|
+
patch: Net::HTTP::Patch
|
|
79
|
+
}.freeze
|
|
80
|
+
|
|
81
|
+
attr_reader :configuration, :certificates, :token_store
|
|
82
|
+
|
|
83
|
+
def initialize(configuration: Swoosh.configuration)
|
|
84
|
+
@configuration = configuration
|
|
85
|
+
@certificates = Certificates.new(configuration)
|
|
86
|
+
@token_store = TokenStore.new(configuration.token_store, ttl: configuration.token_ttl)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def environment = configuration.environment
|
|
90
|
+
def url = configuration.production? ? PRODUCTION_URL : TEST_URL
|
|
91
|
+
def lookup_url = configuration.production? ? PRODUCTION_LOOKUP_URL : TEST_LOOKUP_URL
|
|
92
|
+
|
|
93
|
+
# Swoosh.generate_payment(100, callback_url: "https://example.com/swish")
|
|
94
|
+
#
|
|
95
|
+
# Omit payer_alias for the Swish-app (m-commerce) flow and the payment comes
|
|
96
|
+
# back carrying a token; supply one and Swish notifies that number instead.
|
|
97
|
+
def generate_payment(amount, **options)
|
|
98
|
+
id = uuid
|
|
99
|
+
response = request(:put, "#{url}/#{id}", body: JSON.generate(data(amount, **options)))
|
|
100
|
+
# Net::HTTP matches header names case-insensitively, which matters: Swish
|
|
101
|
+
# sends this back as "Paymentrequesttoken".
|
|
102
|
+
token = response["PaymentRequestToken"]
|
|
103
|
+
token_store.write(id, token)
|
|
104
|
+
|
|
105
|
+
Payment.new({ "id" => id, "status" => Payment::CREATED }, token: token)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def find_payment(id)
|
|
109
|
+
Payment.from_json(request(:get, "#{lookup_url}/#{id}").body.to_s)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Withdraws a payment request the payer hasn't answered yet, so an abandoned
|
|
113
|
+
# checkout stops waiting out the full three minutes.
|
|
114
|
+
#
|
|
115
|
+
# Only a CREATED payment can be cancelled, so expect the race: the payer
|
|
116
|
+
# accepts between your decision to cancel and this request arriving. Swish
|
|
117
|
+
# reports that as PaymentNotCancellable and a second cancel as
|
|
118
|
+
# PaymentAlreadyCancelled -- believe find_payment over your own assumption
|
|
119
|
+
# about which state the payment was in.
|
|
120
|
+
def cancel_payment(id)
|
|
121
|
+
response = request(
|
|
122
|
+
:patch, "#{lookup_url}/#{id}",
|
|
123
|
+
body: JSON.generate(CANCEL_PATCH),
|
|
124
|
+
content_type: JSON_PATCH_CONTENT_TYPE
|
|
125
|
+
)
|
|
126
|
+
# The token dies with the request; keeping it would let token_for hand back
|
|
127
|
+
# something that still renders a QR nobody can pay.
|
|
128
|
+
token_store.delete(id)
|
|
129
|
+
|
|
130
|
+
Payment.from_json(response.body.to_s)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# The token stored when the payment was created, or nil. Nil means "create a
|
|
134
|
+
# fresh payment request" -- never an error.
|
|
135
|
+
def token_for(payment_id) = token_store.read(payment_id)
|
|
136
|
+
|
|
137
|
+
def data(amount, **options)
|
|
138
|
+
PaymentRequest.new(configuration: configuration, amount: amount, **options).to_h
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def uuid = SecureRandom.uuid.delete("-").upcase
|
|
142
|
+
|
|
143
|
+
def cert = certificates.cert
|
|
144
|
+
def root_cert = certificates.root_ca
|
|
145
|
+
|
|
146
|
+
# A connection carrying the merchant certificate, ready to issue one
|
|
147
|
+
# request. Net::HTTP opens and closes it around #request on its own.
|
|
148
|
+
def connection(uri)
|
|
149
|
+
certificates.configure_ssl(Net::HTTP.new(uri.host, uri.port))
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
private
|
|
153
|
+
|
|
154
|
+
def request(verb, target, body: nil, content_type: JSON_CONTENT_TYPE)
|
|
155
|
+
uri = URI.parse(target)
|
|
156
|
+
response = connection(uri).request(build_request(verb, uri, body, content_type))
|
|
157
|
+
return response if response.is_a?(Net::HTTPSuccess)
|
|
158
|
+
|
|
159
|
+
raise ResponseError.for(status: response.code.to_i, body: response.body.to_s)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def build_request(verb, uri, body, content_type)
|
|
163
|
+
REQUEST_CLASSES.fetch(verb).new(uri).tap do |request|
|
|
164
|
+
request["Accept"] = JSON_CONTENT_TYPE
|
|
165
|
+
next if body.nil?
|
|
166
|
+
|
|
167
|
+
request["Content-Type"] = content_type
|
|
168
|
+
request.body = body
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: swoosh
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Johan Halse
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: 'Swoosh drives the merchant API behind Swish, the Swedish mobile payment
|
|
13
|
+
service: create a payment request, poll it, cancel it, and verify the callback Swish
|
|
14
|
+
posts when it settles. It resolves the mutual-TLS certificate your bank issues and
|
|
15
|
+
verifies Swish in return, and carries the m-commerce token that app-switch URLs
|
|
16
|
+
and QR codes are built from. Rails applications get a railtie and a controller concern;
|
|
17
|
+
plain Ruby, Sinatra and Hanami use the same API without them. No runtime dependencies.'
|
|
18
|
+
email:
|
|
19
|
+
- johan@hal.se
|
|
20
|
+
executables: []
|
|
21
|
+
extensions: []
|
|
22
|
+
extra_rdoc_files: []
|
|
23
|
+
files:
|
|
24
|
+
- ".rubocop.yml"
|
|
25
|
+
- ".tool-versions"
|
|
26
|
+
- AGENTS.md
|
|
27
|
+
- CHANGELOG.md
|
|
28
|
+
- Gemfile
|
|
29
|
+
- LICENSE.txt
|
|
30
|
+
- README.md
|
|
31
|
+
- Rakefile
|
|
32
|
+
- bin/console
|
|
33
|
+
- bin/setup
|
|
34
|
+
- certs/Swish_Merchant_TestCertificate_1234679304.csr
|
|
35
|
+
- certs/Swish_Merchant_TestCertificate_1234679304.key
|
|
36
|
+
- certs/Swish_Merchant_TestCertificate_1234679304.p12
|
|
37
|
+
- certs/Swish_Merchant_TestCertificate_1234679304.pem
|
|
38
|
+
- certs/Swish_Merchant_TestSigningCertificate_1234679304.csr
|
|
39
|
+
- certs/Swish_Merchant_TestSigningCertificate_1234679304.key
|
|
40
|
+
- certs/Swish_Merchant_TestSigningCertificate_1234679304.p12
|
|
41
|
+
- certs/Swish_Merchant_TestSigningCertificate_1234679304.pem
|
|
42
|
+
- certs/Swish_TLS_RootCA.pem
|
|
43
|
+
- certs/Swish_TechnicalSupplier_TestCertificate_9870474641.csr
|
|
44
|
+
- certs/Swish_TechnicalSupplier_TestCertificate_9870474641.key
|
|
45
|
+
- certs/Swish_TechnicalSupplier_TestCertificate_9870474641.p12
|
|
46
|
+
- certs/Swish_TechnicalSupplier_TestCertificate_9870474641.pem
|
|
47
|
+
- lib/swoosh.rb
|
|
48
|
+
- lib/swoosh/callback.rb
|
|
49
|
+
- lib/swoosh/callback/controller.rb
|
|
50
|
+
- lib/swoosh/certificates.rb
|
|
51
|
+
- lib/swoosh/configuration.rb
|
|
52
|
+
- lib/swoosh/errors.rb
|
|
53
|
+
- lib/swoosh/payment.rb
|
|
54
|
+
- lib/swoosh/payment_request.rb
|
|
55
|
+
- lib/swoosh/qr_code.rb
|
|
56
|
+
- lib/swoosh/railtie.rb
|
|
57
|
+
- lib/swoosh/test.rb
|
|
58
|
+
- lib/swoosh/token_store.rb
|
|
59
|
+
- lib/swoosh/version.rb
|
|
60
|
+
homepage: https://github.com/johanhalse/swoosh
|
|
61
|
+
licenses:
|
|
62
|
+
- MIT
|
|
63
|
+
metadata:
|
|
64
|
+
allowed_push_host: https://rubygems.org
|
|
65
|
+
rubygems_mfa_required: 'true'
|
|
66
|
+
homepage_uri: https://github.com/johanhalse/swoosh
|
|
67
|
+
source_code_uri: https://github.com/johanhalse/swoosh
|
|
68
|
+
changelog_uri: https://github.com/johanhalse/swoosh/CHANGELOG.md
|
|
69
|
+
rdoc_options: []
|
|
70
|
+
require_paths:
|
|
71
|
+
- lib
|
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - ">="
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: 3.2.0
|
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
requirements: []
|
|
83
|
+
rubygems_version: 4.0.20
|
|
84
|
+
specification_version: 4
|
|
85
|
+
summary: Swish payments for Ruby, with first-class Rails support
|
|
86
|
+
test_files: []
|