Skip to content

Commit a698506

Browse files
annaswimscoorasse
andauthored
Many tickets (coorasse#27)
* Enhanced examples * create an example with user data * fix date formatting in example * remove invalid app_launch_url from example * fix dev host * Enable multiple passes/tickets in a single link * Update test/api/v1/test_passes_controller.rb Co-authored-by: Alessandro Rodi <[email protected]> --------- Co-authored-by: Alessandro Rodi <[email protected]>
1 parent c7ef36a commit a698506

File tree

11 files changed

+189
-17
lines changed

11 files changed

+189
-17
lines changed

README.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,19 @@ to be included in the pass.
136136
### Serve your Wallet Pass
137137

138138
Use the [Passkit::UrlGenerator](lib/passkit/url_generator.rb) to generate the URL to serve your Wallet Pass.
139-
You can initialize it with:
139+
For one pass, you can initialize it with:
140140

141141
```ruby
142142
Passkit::UrlGenerator.new(Passkit::MyPass, User.find(1))
143143
```
144144

145+
For one passes, you can initialize it with:
146+
147+
```ruby
148+
Passkit::UrlGenerator.new(Passkit::UserTicket, User.find(1), :tickets)
149+
```
150+
(this presumes you have `User.find(1).tickets` would return the ticket records)
151+
145152
and then use `.android` or `.ios` to get the URL to serve the Wallet Pass.
146153
Again, check the example mailer included in the gem to see how to use it.
147154

app/controllers/passkit/api/v1/passes_controller.rb

+19-8
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,18 @@ class PassesController < ActionController::API
55
before_action :decrypt_payload, only: :create
66

77
def create
8-
send_file(fetch_pass(@payload), type: "application/vnd.apple.pkpass", disposition: "attachment")
8+
set_generator
9+
10+
if @generator && @payload[:collection_name].present?
11+
files = @generator.public_send(@payload[:collection_name]).collect do |collection_item|
12+
Passkit::Factory.create_pass(@payload[:pass_class], collection_item)
13+
end
14+
file = Passkit::Generator.compress_passes_files(files)
15+
send_file(file, type: 'application/vnd.apple.pkpasses', disposition: 'attachment')
16+
else
17+
file = Passkit::Factory.create_pass(@payload[:pass_class], @generator)
18+
send_file(file, type: 'application/vnd.apple.pkpass', disposition: 'attachment')
19+
end
920
end
1021

1122
# @return If request is authorized, returns HTTP status 200 with a payload of the pass data.
@@ -44,13 +55,13 @@ def decrypt_payload
4455
end
4556
end
4657

47-
def fetch_pass(payload)
48-
generator = nil
49-
if payload[:generator_class].present? && payload[:generator_id].present?
50-
generator_class = payload[:generator_class].constantize
51-
generator = generator_class.find(payload[:generator_id])
52-
end
53-
Passkit::Factory.create_pass(payload[:pass_class], generator)
58+
def set_generator
59+
@generator = nil
60+
61+
return unless @payload[:generator_class].present? && @payload[:generator_id].present?
62+
63+
generator_class = @payload[:generator_class].constantize
64+
@generator = generator_class.find(@payload[:generator_id])
5465
end
5566
end
5667
end

lib/passkit/base_pass.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ def voided
9595
def add_other_files(path)
9696
end
9797

98-
# Distance in meters from locations; if blank uses pass default
98+
# Distance in meters from locations; if blank uses pass default.
99+
# The system uses the smaller of either this distance or the default distance.
99100
def max_distance
100101
end
101102

lib/passkit/generator.rb

+13
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,19 @@ def generate_and_sign
2323
compress_pass_file
2424
end
2525

26+
def self.compress_passes_files(files)
27+
zip_path = TMP_FOLDER.join("#{SecureRandom.uuid}.pkpasses")
28+
zipped_file = File.open(zip_path, "w")
29+
30+
Zip::OutputStream.open(zipped_file.path) do |z|
31+
files.each do |file|
32+
z.put_next_entry(File.basename(file))
33+
z.print File.read(file)
34+
end
35+
end
36+
zip_path
37+
end
38+
2639
private
2740

2841
def check_necessary_files

lib/passkit/payload_generator.rb

+5-4
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@ module Passkit
22
class PayloadGenerator
33
VALIDITY = 30.days
44

5-
def self.encrypted(pass_class, generator = nil)
6-
UrlEncrypt.encrypt(hash(pass_class, generator))
5+
def self.encrypted(pass_class, generator = nil, collection_name = nil)
6+
UrlEncrypt.encrypt(hash(pass_class, generator, collection_name))
77
end
88

9-
def self.hash(pass_class, generator = nil)
9+
def self.hash(pass_class, generator = nil, collection_name = nil)
1010
valid_until = VALIDITY.from_now
1111

1212
{valid_until: valid_until,
1313
generator_class: generator&.class&.name,
1414
generator_id: generator&.id,
15-
pass_class: pass_class.name}
15+
pass_class: pass_class.name,
16+
collection_name: collection_name}
1617
end
1718
end
1819
end

lib/passkit/url_generator.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ module Passkit
22
class UrlGenerator
33
include Passkit::Engine.routes.url_helpers
44

5-
def initialize(pass_class, generator = nil)
5+
def initialize(pass_class, generator = nil, collection_name = nil)
66
@url = passes_api_url(host: ENV["PASSKIT_WEB_SERVICE_HOST"],
7-
payload: PayloadGenerator.encrypted(pass_class, generator))
7+
payload: PayloadGenerator.encrypted(pass_class, generator, collection_name))
88
end
99

1010
def ios

test/api/v1/test_passes_controller.rb

+11
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ def test_create
1818
assert_equal 7, zip_file.size
1919
end
2020

21+
def test_create_collection
22+
payload = Passkit::PayloadGenerator.encrypted(Passkit::UserTicket, User.find(1), :tickets)
23+
get passes_api_path(payload)
24+
assert_response :success
25+
assert_equal 2, Passkit::Pass.count
26+
unzipped_passes = Zip::File.open_buffer(StringIO.new(response.body))
27+
assert_equal 2, unzipped_passes.size # the main zip file contains two passes
28+
unzipped_pass = Zip::File.open_buffer(unzipped_passes.first.zipfile)
29+
assert_includes unzipped_passes.first.name, '.pkpass'
30+
end
31+
2132
def test_show
2233
_pkpass = Passkit::Factory.create_pass(Passkit::ExampleStoreCard)
2334
assert_equal 1, Passkit::Pass.count
+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
module Passkit
2+
class UserTicket < BasePass
3+
def pass_type
4+
:eventTicket
5+
end
6+
7+
def organization_name
8+
"Passkit"
9+
end
10+
11+
def description
12+
"A basic description for a pass"
13+
end
14+
15+
# A pass can have up to ten relevant locations
16+
#
17+
# @see https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/PassKit_PG/Creating.html
18+
def locations
19+
[
20+
{ latitude: 41.2273414693647, longitude: -95.92925748878405 }, # North Entrance
21+
{ latitude: 41.22476226066285, longitude: -95.92879374051269 } # Main Entrance
22+
]
23+
end
24+
25+
def file_name
26+
@file_name ||= SecureRandom.uuid
27+
end
28+
29+
# QRCode by default
30+
def barcodes
31+
[
32+
{ messageEncoding: "iso-8859-1",
33+
format: "PKBarcodeFormatQR",
34+
message: "https://github.com/coorasse/passkit",
35+
altText: "https://github.com/coorasse/passkit" }
36+
]
37+
end
38+
39+
# Barcode example
40+
# def barcode
41+
# { messageEncoding: 'iso-8859-1',
42+
# format: 'PKBarcodeFormatCode128',
43+
# message: '12345',
44+
# altText: '12345' }
45+
# end
46+
47+
def logo_text
48+
"Loyalty Card"
49+
end
50+
51+
def expiration_date
52+
# Expire the pass tomorrow
53+
(Time.current + 60*60*24).strftime('%Y-%m-%dT%H:%M:%S%:z')
54+
end
55+
56+
def semantics
57+
{
58+
balance: {
59+
amount: "100",
60+
currencyCode: "USD"
61+
}
62+
}
63+
end
64+
65+
def header_fields
66+
[{
67+
key: "balance",
68+
label: "Balance",
69+
value: 100,
70+
currencyCode: "$"
71+
}]
72+
end
73+
74+
def back_fields
75+
[{
76+
key: "example1",
77+
label: "Code",
78+
value: "0123456789"
79+
},
80+
{
81+
key: "example2",
82+
label: "Creator",
83+
value: "https://github.com/coorasse"
84+
},
85+
{
86+
key: "example3",
87+
label: "Contact",
88+
89+
}]
90+
end
91+
92+
def auxiliary_fields
93+
[{
94+
key: "name",
95+
label: "Name",
96+
value: @generator.name
97+
},
98+
{
99+
key: "email",
100+
label: "Email",
101+
value: "#{@generator.name}@hey.com"
102+
}]
103+
end
104+
105+
private
106+
107+
def folder_name
108+
'user_store_card'
109+
end
110+
end
111+
end

test/dummy/test/fixtures/tickets.yml

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
first_ticket:
2+
name: FirstUser Ticket1
3+
user_id: 1
4+
5+
second_ticket:
6+
name: FirstUser Ticket2
7+
user_id: 1
8+
9+
third_ticket:
10+
name: SecondUser Ticket1
11+
user_id: 2

test/dummy/test/fixtures/users.yml

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
one:
2+
id: 1
3+
name: first user
4+
5+
two:
6+
id: 2
7+
name: second user

test/rails_helper.rb

-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
# Load fixtures from the engine
2121
if ActiveSupport::TestCase.respond_to?(:fixture_path=)
22-
ActiveSupport::TestCase.fixture_path = File.expand_path("fixtures", __dir__)
2322
ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
2423
ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files"
2524
ActiveSupport::TestCase.fixtures :all

0 commit comments

Comments
 (0)