kiosk-server 0.5.20 → 0.6.1
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 +4 -4
- data/CHANGELOG.md +58 -0
- data/README.md +63 -41
- data/app/controllers/kiosk/server/auth_controller.rb +18 -62
- data/app/controllers/kiosk/server/discovery_controller.rb +25 -3
- data/app/controllers/kiosk/server/jwks_controller.rb +2 -2
- data/app/controllers/kiosk/server/kyc_attestation_controller.rb +1 -4
- data/app/controllers/kiosk/server/oauth_device_authorization_controller.rb +11 -29
- data/app/controllers/kiosk/server/oauth_registration_controller.rb +34 -0
- data/app/controllers/kiosk/server/oauth_token_controller.rb +33 -32
- data/app/models/kiosk/checkout_mandate.rb +22 -0
- data/app/models/kiosk/settlement.rb +1 -1
- data/lib/generators/kiosk/install/install_generator.rb +0 -5
- data/lib/generators/kiosk/install/templates/create_kiosk_mandates.rb.tt +4 -5
- data/lib/kiosk/payable.rb +19 -25
- data/lib/kiosk/server/account_binding.rb +19 -58
- data/lib/kiosk/server/agent_identity_providers/default_agent_idp.rb +18 -16
- data/lib/kiosk/server/agent_rotation.rb +62 -0
- data/lib/kiosk/server/assistant_key.rb +86 -0
- data/lib/kiosk/server/auth_toll.rb +1 -1
- data/lib/kiosk/server/binding_module_gate.rb +1 -1
- data/lib/kiosk/server/checkout.rb +57 -0
- data/lib/kiosk/server/client.rb +61 -0
- data/lib/kiosk/server/client_assertion.rb +153 -0
- data/lib/kiosk/server/client_registration.rb +94 -0
- data/lib/kiosk/server/configuration_extension.rb +56 -44
- data/lib/kiosk/server/device_code_grant.rb +38 -69
- data/lib/kiosk/server/dpop.rb +121 -0
- data/lib/kiosk/server/eddsa.rb +38 -0
- data/lib/kiosk/server/engine.rb +14 -11
- data/lib/kiosk/server/errors.rb +7 -0
- data/lib/kiosk/server/events_connection.rb +15 -5
- data/lib/kiosk/server/executor/repeat.rb +13 -12
- data/lib/kiosk/server/executor/spending_cap.rb +25 -26
- data/lib/kiosk/server/executor.rb +82 -101
- data/lib/kiosk/server/identity_resolution.rb +12 -3
- data/lib/kiosk/server/jwk.rb +83 -0
- data/lib/kiosk/server/jwks.rb +6 -0
- data/lib/kiosk/server/jwt_issuer.rb +7 -13
- data/lib/kiosk/server/kyc_verifier.rb +2 -2
- data/lib/kiosk/server/link_code.rb +9 -18
- data/lib/kiosk/server/linked_assistants.rb +1 -1
- data/lib/kiosk/server/mandate_verifier.rb +97 -139
- data/lib/kiosk/server/oauth_answers.rb +30 -0
- data/lib/kiosk/server/oauth_error.rb +30 -0
- data/lib/kiosk/server/open_api.rb +22 -20
- data/lib/kiosk/server/payment_claim.rb +44 -75
- data/lib/kiosk/server/receipts.rb +29 -0
- data/lib/kiosk/server/request_validation.rb +1 -2
- data/lib/kiosk/server/revocation_stores.rb +8 -0
- data/lib/kiosk/server/schema_definitions.rb +33 -64
- data/lib/kiosk/server/schemas/auth.schema.json +368 -57
- data/lib/kiosk/server/schemas/binding.schema.json +23 -58
- data/lib/kiosk/server/schemas/kyc.schema.json +19 -76
- data/lib/kiosk/server/schemas/mandates.schema.json +122 -38
- data/lib/kiosk/server/sd_jwt.rb +104 -0
- data/lib/kiosk/server/signing_key.rb +18 -52
- data/lib/kiosk/server/verb_wire.rb +1 -1
- data/lib/kiosk/server/version.rb +1 -1
- data/lib/kiosk/server/well_known.rb +268 -204
- data/lib/kiosk/server/wire_rendering.rb +1 -1
- data/lib/kiosk/server.rb +2 -0
- data/lib/kiosk/skill_link.rb +6 -2
- data/listen.py +38 -8
- metadata +26 -17
- data/app/models/kiosk/cart_mandate.rb +0 -21
- data/lib/generators/kiosk/install/templates/create_kiosk_auth_challenges.rb.tt +0 -16
- data/lib/kiosk/server/agent_login.rb +0 -49
- data/lib/kiosk/server/agent_registration.rb +0 -88
- data/lib/kiosk/server/auth_challenge.rb +0 -33
- data/lib/kiosk/server/auth_challenge_stores.rb +0 -74
- data/lib/kiosk/server/pop_verifier.rb +0 -87
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'kiosk/uuid_check'
|
|
4
|
-
|
|
5
3
|
module Kiosk
|
|
6
4
|
module Server
|
|
7
5
|
# §11.6 over a PSP adapter: claims the operator's payable row `unpaid →
|
|
8
|
-
# paying` before the capture, checks the
|
|
6
|
+
# paying` before the capture, checks the checkout against the operator's price,
|
|
9
7
|
# flips the row to `paid` once the capture returns, tells its owner on the
|
|
10
8
|
# `payment` topic, and runs `c.after_payment` once the settlement is recorded.
|
|
11
9
|
# A refund through it is recorded against the settlement.
|
|
@@ -19,7 +17,7 @@ module Kiosk
|
|
|
19
17
|
attr_reader :currency, :reference
|
|
20
18
|
|
|
21
19
|
# @param model [String] the payable model's name, e.g. "Order"; it includes Kiosk::Owned and Kiosk::Payable
|
|
22
|
-
# @param reference [String] the
|
|
20
|
+
# @param reference [String] the checkout line-item key naming the row, e.g. "order_id"
|
|
23
21
|
# @param query [String] the per-user query that publishes the row's payment state
|
|
24
22
|
def initialize(psp, currency:, model:, reference:, query:)
|
|
25
23
|
@psp = psp
|
|
@@ -34,12 +32,12 @@ module Kiosk
|
|
|
34
32
|
# The same claim over another PSP adapter.
|
|
35
33
|
def over(psp) = self.class.new(psp, **@options)
|
|
36
34
|
|
|
37
|
-
def capture(
|
|
35
|
+
def capture(checkout_mandate, payment_method: nil) = charge_row(claim!(checkout_mandate), checkout_mandate, payment_method)
|
|
38
36
|
|
|
39
37
|
delegate :setup_required?, :setup_url, to: :@psp
|
|
40
38
|
|
|
41
|
-
# The settlements whose
|
|
42
|
-
def settlements(id) = Kiosk::Settlement.joins(:
|
|
39
|
+
# The settlements whose checkout names this row.
|
|
40
|
+
def settlements(id) = Kiosk::Settlement.joins(:checkout_mandate).merge(Kiosk::CheckoutMandate.referencing(@reference => id))
|
|
43
41
|
|
|
44
42
|
def settled?(id) = settlements(id).exists?
|
|
45
43
|
|
|
@@ -58,15 +56,15 @@ module Kiosk
|
|
|
58
56
|
|
|
59
57
|
private
|
|
60
58
|
|
|
61
|
-
def charge_row(id,
|
|
62
|
-
settled = charge(id,
|
|
59
|
+
def charge_row(id, checkout_mandate, payment_method)
|
|
60
|
+
settled = charge(id, checkout_mandate, payment_method)
|
|
63
61
|
mark_paid!(id)
|
|
64
62
|
settled
|
|
65
63
|
end
|
|
66
64
|
|
|
67
65
|
# Releases the claim when the PSP refuses before any money moved.
|
|
68
|
-
def charge(id,
|
|
69
|
-
@psp.capture(
|
|
66
|
+
def charge(id, checkout_mandate, payment_method)
|
|
67
|
+
@psp.capture(checkout_mandate, payment_method: payment_method)
|
|
70
68
|
rescue Kiosk::PaymentProviders::SetupRequired
|
|
71
69
|
release!(id)
|
|
72
70
|
raise
|
|
@@ -75,11 +73,11 @@ module Kiosk
|
|
|
75
73
|
raise
|
|
76
74
|
end
|
|
77
75
|
|
|
78
|
-
def claim!(
|
|
79
|
-
id =
|
|
80
|
-
refuse_unclaimable!(id,
|
|
76
|
+
def claim!(checkout)
|
|
77
|
+
id = Lines.reference(checkout.line_items, @reference)
|
|
78
|
+
refuse_unclaimable!(id, checkout.user_id.to_s) unless @row.take(id, checkout.user_id.to_s, from: UNPAID, to: PAYING).positive?
|
|
81
79
|
begin
|
|
82
|
-
check_price!(
|
|
80
|
+
check_price!(checkout, id)
|
|
83
81
|
rescue StandardError
|
|
84
82
|
release!(id)
|
|
85
83
|
raise
|
|
@@ -87,28 +85,20 @@ module Kiosk
|
|
|
87
85
|
id
|
|
88
86
|
end
|
|
89
87
|
|
|
90
|
-
# The
|
|
91
|
-
def
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def check_price!(cart, id)
|
|
97
|
-
total = cart.total_amount_cents.to_i
|
|
98
|
-
lines = Array(cart.line_items).reject { |li| li[@reference] }
|
|
99
|
-
Lines.check_sum!(total, lines)
|
|
100
|
-
|
|
101
|
-
price = checker.call(id, lines)
|
|
88
|
+
# The price may have moved since the checkout was signed.
|
|
89
|
+
def check_price!(checkout, id)
|
|
90
|
+
total = checkout.total_amount_cents
|
|
91
|
+
price = checker.call(id, checkout.line_items.reject { |li| li[@reference] })
|
|
102
92
|
deny price if price.is_a?(String)
|
|
103
93
|
return if total == price
|
|
104
94
|
|
|
105
|
-
deny "
|
|
106
|
-
'
|
|
95
|
+
deny "checkout total #{total} does not equal this operator's price #{price} for #{noun} #{id}: " \
|
|
96
|
+
'the price changed after the operator signed the checkout'
|
|
107
97
|
end
|
|
108
98
|
|
|
109
99
|
def checker
|
|
110
|
-
Kiosk.configuration.
|
|
111
|
-
raise Errors::ConfigurationError, "PaymentClaim needs c.
|
|
100
|
+
Kiosk.configuration.checkout_price_checker or
|
|
101
|
+
raise Errors::ConfigurationError, "PaymentClaim needs c.checkout_price_checker, the operator's price"
|
|
112
102
|
end
|
|
113
103
|
|
|
114
104
|
def refuse_unclaimable!(id, payer)
|
|
@@ -123,8 +113,8 @@ module Kiosk
|
|
|
123
113
|
mark_paid!(id)
|
|
124
114
|
deny "#{noun} #{id} is already paid"
|
|
125
115
|
end
|
|
126
|
-
deny "#{noun} #{id} has a payment in progress under
|
|
127
|
-
'byte for byte:
|
|
116
|
+
deny "#{noun} #{id} has a payment in progress under other mandates. Send those mandates again, " \
|
|
117
|
+
'byte for byte: they cannot charge twice. Do not sign new mandates. ' \
|
|
128
118
|
"GET <endpoint>/#{@query} shows the payment state."
|
|
129
119
|
end
|
|
130
120
|
|
|
@@ -134,62 +124,41 @@ module Kiosk
|
|
|
134
124
|
raise Errors::Forbidden, message
|
|
135
125
|
end
|
|
136
126
|
|
|
137
|
-
# The
|
|
127
|
+
# The checkout's line items: the one row they name.
|
|
138
128
|
module Lines
|
|
139
129
|
module_function
|
|
140
130
|
|
|
141
131
|
def reference(line_items, key)
|
|
142
|
-
refs =
|
|
143
|
-
|
|
144
|
-
id = refs.first
|
|
145
|
-
return id if Kiosk::UuidCheck.valid?(id)
|
|
146
|
-
|
|
147
|
-
raise Errors::BadRequest.new("cart line_items #{key} #{id.inspect} is not a uuid",
|
|
148
|
-
hint: "use the `#{key}` this operator returned, verbatim")
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
# The priced lines, when any, sum to the cart total.
|
|
152
|
-
def check_sum!(total, lines)
|
|
153
|
-
priced = lines.select { |li| li['price_cents'] }
|
|
154
|
-
return if priced.empty?
|
|
155
|
-
|
|
156
|
-
sum = priced.sum do |li|
|
|
157
|
-
qty = li['qty'].to_i
|
|
158
|
-
price = li['price_cents'].to_i
|
|
159
|
-
deny 'each priced line needs a positive qty and price_cents' if qty <= 0 || price <= 0
|
|
160
|
-
qty * price
|
|
161
|
-
end
|
|
162
|
-
deny "cart total #{total} does not equal the sum of its line items #{sum}" unless total == sum
|
|
163
|
-
end
|
|
132
|
+
refs = line_items.filter_map { |li| li[key] }.uniq
|
|
133
|
+
return refs.first.to_s if refs.one?
|
|
164
134
|
|
|
165
|
-
|
|
166
|
-
raise Errors::Forbidden, message
|
|
135
|
+
raise Errors::Forbidden, "checkout line_items must name exactly one #{key}"
|
|
167
136
|
end
|
|
168
137
|
end
|
|
169
138
|
|
|
170
|
-
# A repeat of
|
|
171
|
-
# the newest unfinished
|
|
172
|
-
#
|
|
139
|
+
# A repeat of mandates whose capture did not complete. The row stays with
|
|
140
|
+
# the newest unfinished checkout mandate, and the PSP keys the capture by
|
|
141
|
+
# its reference, so the repeat cannot charge twice.
|
|
173
142
|
module Resume
|
|
174
|
-
def resume(
|
|
143
|
+
def resume(checkout_mandate, payment_method: nil) = charge_row(reclaim!(checkout_mandate), checkout_mandate, payment_method)
|
|
175
144
|
|
|
176
145
|
private
|
|
177
146
|
|
|
178
|
-
# The row
|
|
179
|
-
def reclaim!(
|
|
180
|
-
id =
|
|
181
|
-
payer =
|
|
147
|
+
# The row these repeated mandates claimed, or the row they may claim now.
|
|
148
|
+
def reclaim!(checkout)
|
|
149
|
+
id = Lines.reference(checkout.line_items, @reference)
|
|
150
|
+
payer = checkout.user_id.to_s
|
|
182
151
|
status = @row.status(id, payer)
|
|
183
|
-
return claim!(
|
|
184
|
-
return id if [PAYING, PAID].include?(status) && !settled?(id) &&
|
|
152
|
+
return claim!(checkout) if status == UNPAID
|
|
153
|
+
return id if [PAYING, PAID].include?(status) && !settled?(id) && unfinished_mandate(id, payer) == checkout.reference
|
|
185
154
|
|
|
186
155
|
refuse_unclaimable!(id, payer)
|
|
187
156
|
end
|
|
188
157
|
|
|
189
|
-
# The
|
|
190
|
-
def
|
|
191
|
-
Kiosk::
|
|
192
|
-
|
|
158
|
+
# The reference of the newest checkout mandate for this row that is neither settled nor declined.
|
|
159
|
+
def unfinished_mandate(id, payer)
|
|
160
|
+
Kiosk::CheckoutMandate.referencing(@reference => id).where(user_id: payer, declined_at: nil)
|
|
161
|
+
.where.missing(:settlements).order(created_at: :desc, id: :desc).pick(:reference)
|
|
193
162
|
end
|
|
194
163
|
end
|
|
195
164
|
include Resume
|
|
@@ -218,10 +187,10 @@ module Kiosk
|
|
|
218
187
|
# The operator's work after a payment, which reads the settlement.
|
|
219
188
|
module AfterPayment
|
|
220
189
|
# The executor calls this once, after it records the settlement of
|
|
221
|
-
# `
|
|
190
|
+
# `checkout_mandate`, so `c.after_payment` reads the row's `settlement`. A
|
|
222
191
|
# failure here never surfaces on the wire: the log carries it.
|
|
223
|
-
def settled(
|
|
224
|
-
id = Lines.reference(
|
|
192
|
+
def settled(checkout_mandate)
|
|
193
|
+
id = Lines.reference(checkout_mandate.line_items, @reference)
|
|
225
194
|
Kiosk.configuration.after_payment&.call(id)
|
|
226
195
|
rescue StandardError => e
|
|
227
196
|
FailureLog.report("#{self.class} after_payment failed for #{id}", e)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kiosk
|
|
4
|
+
module Server
|
|
5
|
+
# The two AP2 v0.2 receipts a settled `pay` answers (§11.3): JWTs this
|
|
6
|
+
# operator signs as it signs a checkout, each naming its mandate by reference.
|
|
7
|
+
module Receipts
|
|
8
|
+
SUCCESS = 'Success'
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
# @return [Hash] `checkout_receipt` and `payment_receipt`, compact JWTs
|
|
13
|
+
def issue(checkout:, payment:, settlement_id:, psp_reference:, network_confirmation_id:)
|
|
14
|
+
{ checkout_receipt: sign(checkout, order_id: order_id(checkout)),
|
|
15
|
+
payment_receipt: sign(payment, payment_id: settlement_id, psp_confirmation_id: psp_reference,
|
|
16
|
+
network_confirmation_id: network_confirmation_id) }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# The id in the checkout's first line item, which names the item paid for (§11.7).
|
|
20
|
+
def order_id(checkout) = checkout.line_items.first.values.first.to_s
|
|
21
|
+
|
|
22
|
+
# A network id the PSP did not report is left out.
|
|
23
|
+
def sign(mandate, **claims)
|
|
24
|
+
Checkout.signed({ status: SUCCESS, iss: Kiosk.current_issuer, iat: Time.now.to_i,
|
|
25
|
+
reference: mandate.reference, **claims }.compact)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -15,8 +15,7 @@ module Kiosk
|
|
|
15
15
|
# RESERVED-PLANE REQUEST BODY => the published object that types it.
|
|
16
16
|
# The `/oauth/*` requests are form-encoded and have no schema.
|
|
17
17
|
BODY_SCHEMAS = {
|
|
18
|
-
'POST <endpoint>/auth/
|
|
19
|
-
'POST <endpoint>/auth/login' => 'auth.schema.json#/$defs/credentialRequest',
|
|
18
|
+
'POST <endpoint>/auth/rotate' => 'auth.schema.json#/$defs/rotationRequest',
|
|
20
19
|
'POST <endpoint>/auth/claim' => 'binding.schema.json#/$defs/claimRequest',
|
|
21
20
|
'POST <endpoint>/auth/unlink' => 'binding.schema.json#/$defs/unlinkRequest',
|
|
22
21
|
'POST <endpoint>/agents/kyc' => 'kyc.schema.json#/$defs/request',
|
|
@@ -7,6 +7,14 @@ module Kiosk
|
|
|
7
7
|
# `revoked?(agent_id:, iat:)`: a token whose `iat` is strictly before the
|
|
8
8
|
# agent's watermark is revoked.
|
|
9
9
|
module RevocationStores
|
|
10
|
+
# Revokes every token of `agent_id` issued so far. JWT `iat` is whole
|
|
11
|
+
# seconds, the check is `iat < watermark`, and the IdP dates a token no
|
|
12
|
+
# earlier than the watermark, so the new watermark is the second after
|
|
13
|
+
# both now and the old watermark.
|
|
14
|
+
def self.revoke_issued(agent_id, store: Kiosk.configuration.revocation_store)
|
|
15
|
+
store.revoke_all(agent_id, at: [Time.now.to_i, store.watermark_for(agent_id).to_i].max + 1)
|
|
16
|
+
end
|
|
17
|
+
|
|
10
18
|
# The default store: the `<schema>.revocations` table, shared by every
|
|
11
19
|
# process and kept across restarts. A token of an unlinked or deleted
|
|
12
20
|
# agent is revoked too.
|
|
@@ -126,37 +126,20 @@ module Kiosk
|
|
|
126
126
|
DDL
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
-
# 005: the AP2 trail. `id` is server-generated;
|
|
130
|
-
#
|
|
131
|
-
# charge refused with no money
|
|
132
|
-
#
|
|
129
|
+
# 005: the AP2 trail. `id` is server-generated; `reference` is the hash of
|
|
130
|
+
# the presented SD-JWT, unique per principal. One checkout holds one live
|
|
131
|
+
# checkout mandate: `declined_at` records a charge refused with no money
|
|
132
|
+
# moved, and frees the checkout. A settlement is server-minted.
|
|
133
133
|
def mandates_sql(schema: nil, user_id_type: nil)
|
|
134
134
|
schema ||= Kiosk.configuration.schema
|
|
135
135
|
user_id_type ||= Kiosk.configuration.user_id_type
|
|
136
136
|
col_type = user_id_cast(user_id_type)
|
|
137
137
|
|
|
138
138
|
<<~DDL.strip
|
|
139
|
-
CREATE TABLE IF NOT EXISTS "#{schema}".
|
|
140
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
141
|
-
mandate_id text NOT NULL,
|
|
142
|
-
user_id #{col_type} NOT NULL,
|
|
143
|
-
agent_id uuid NOT NULL,
|
|
144
|
-
issuer text NOT NULL,
|
|
145
|
-
scope text NOT NULL,
|
|
146
|
-
cap_amount_cents bigint NOT NULL,
|
|
147
|
-
currency text NOT NULL,
|
|
148
|
-
expires_at timestamptz NOT NULL,
|
|
149
|
-
created_at timestamptz NOT NULL DEFAULT now(),
|
|
150
|
-
raw_jws text NOT NULL,
|
|
151
|
-
UNIQUE (user_id, mandate_id)
|
|
152
|
-
);
|
|
153
|
-
CREATE INDEX IF NOT EXISTS idx_intent_mandates_user_id ON "#{schema}".intent_mandates (user_id);
|
|
154
|
-
CREATE INDEX IF NOT EXISTS idx_intent_mandates_agent_id ON "#{schema}".intent_mandates (agent_id);
|
|
155
|
-
|
|
156
|
-
CREATE TABLE IF NOT EXISTS "#{schema}".cart_mandates (
|
|
139
|
+
CREATE TABLE IF NOT EXISTS "#{schema}".checkout_mandates (
|
|
157
140
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
158
|
-
|
|
159
|
-
|
|
141
|
+
reference text NOT NULL,
|
|
142
|
+
checkout_hash text NOT NULL,
|
|
160
143
|
user_id #{col_type} NOT NULL,
|
|
161
144
|
agent_id uuid NOT NULL,
|
|
162
145
|
issuer text NOT NULL,
|
|
@@ -165,45 +148,47 @@ module Kiosk
|
|
|
165
148
|
currency text NOT NULL,
|
|
166
149
|
expires_at timestamptz NOT NULL,
|
|
167
150
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
168
|
-
|
|
151
|
+
sd_jwt text NOT NULL,
|
|
169
152
|
declined_at timestamptz,
|
|
170
|
-
UNIQUE (user_id,
|
|
153
|
+
UNIQUE (user_id, reference)
|
|
171
154
|
);
|
|
172
|
-
CREATE INDEX IF NOT EXISTS
|
|
173
|
-
CREATE INDEX IF NOT EXISTS
|
|
155
|
+
CREATE INDEX IF NOT EXISTS idx_checkout_mandates_user_id ON "#{schema}".checkout_mandates (user_id);
|
|
156
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_checkout_mandates_live_checkout
|
|
157
|
+
ON "#{schema}".checkout_mandates (user_id, checkout_hash) WHERE declined_at IS NULL;
|
|
174
158
|
|
|
175
159
|
CREATE TABLE IF NOT EXISTS "#{schema}".payment_mandates (
|
|
176
|
-
id
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
user_id
|
|
180
|
-
agent_id
|
|
181
|
-
issuer
|
|
182
|
-
|
|
183
|
-
amount_cents
|
|
184
|
-
currency
|
|
185
|
-
expires_at
|
|
186
|
-
created_at
|
|
187
|
-
|
|
188
|
-
UNIQUE (user_id,
|
|
160
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
161
|
+
reference text NOT NULL,
|
|
162
|
+
checkout_mandate_id uuid NOT NULL REFERENCES "#{schema}".checkout_mandates(id) ON DELETE CASCADE,
|
|
163
|
+
user_id #{col_type} NOT NULL,
|
|
164
|
+
agent_id uuid NOT NULL,
|
|
165
|
+
issuer text NOT NULL,
|
|
166
|
+
payment_instrument text NOT NULL,
|
|
167
|
+
amount_cents bigint NOT NULL,
|
|
168
|
+
currency text NOT NULL,
|
|
169
|
+
expires_at timestamptz,
|
|
170
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
171
|
+
sd_jwt text NOT NULL,
|
|
172
|
+
UNIQUE (user_id, reference)
|
|
189
173
|
);
|
|
190
|
-
CREATE INDEX IF NOT EXISTS idx_payment_mandates_user_id
|
|
191
|
-
CREATE INDEX IF NOT EXISTS
|
|
174
|
+
CREATE INDEX IF NOT EXISTS idx_payment_mandates_user_id ON "#{schema}".payment_mandates (user_id);
|
|
175
|
+
CREATE INDEX IF NOT EXISTS idx_payment_mandates_checkout ON "#{schema}".payment_mandates (checkout_mandate_id);
|
|
192
176
|
|
|
193
177
|
CREATE TABLE IF NOT EXISTS "#{schema}".settlements (
|
|
194
178
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
195
|
-
|
|
179
|
+
checkout_mandate_id uuid NOT NULL REFERENCES "#{schema}".checkout_mandates(id) ON DELETE CASCADE,
|
|
196
180
|
user_id #{col_type} NOT NULL,
|
|
197
181
|
agent_id uuid NOT NULL,
|
|
198
182
|
issuer text NOT NULL,
|
|
199
183
|
psp_reference text NOT NULL,
|
|
200
184
|
settled_amount_cents bigint NOT NULL,
|
|
201
185
|
currency text NOT NULL,
|
|
186
|
+
network_confirmation_id text,
|
|
202
187
|
settled_at timestamptz NOT NULL,
|
|
203
|
-
UNIQUE (
|
|
188
|
+
UNIQUE (checkout_mandate_id)
|
|
204
189
|
);
|
|
205
|
-
CREATE INDEX IF NOT EXISTS idx_settlements_user_id
|
|
206
|
-
CREATE INDEX IF NOT EXISTS
|
|
190
|
+
CREATE INDEX IF NOT EXISTS idx_settlements_user_id ON "#{schema}".settlements (user_id);
|
|
191
|
+
CREATE INDEX IF NOT EXISTS idx_settlements_checkout ON "#{schema}".settlements (checkout_mandate_id);
|
|
207
192
|
DDL
|
|
208
193
|
end
|
|
209
194
|
|
|
@@ -257,7 +242,7 @@ module Kiosk
|
|
|
257
242
|
DDL
|
|
258
243
|
end
|
|
259
244
|
|
|
260
|
-
# 008: spent proof-of-work
|
|
245
|
+
# 008: spent ids of proof-of-work challenges and client assertions; the primary key is the single-use gate.
|
|
261
246
|
def pow_spent_sql(schema: nil)
|
|
262
247
|
schema ||= Kiosk.configuration.schema
|
|
263
248
|
|
|
@@ -318,22 +303,6 @@ module Kiosk
|
|
|
318
303
|
DDL
|
|
319
304
|
end
|
|
320
305
|
|
|
321
|
-
# 010: each public key's outstanding auth challenge; the primary key keeps it single.
|
|
322
|
-
def auth_challenge_sql(schema: nil)
|
|
323
|
-
schema ||= Kiosk.configuration.schema
|
|
324
|
-
|
|
325
|
-
<<~DDL.strip
|
|
326
|
-
CREATE TABLE IF NOT EXISTS "#{schema}".auth_challenges (
|
|
327
|
-
public_key text PRIMARY KEY,
|
|
328
|
-
nonce text NOT NULL,
|
|
329
|
-
expires_at timestamptz NOT NULL
|
|
330
|
-
);
|
|
331
|
-
-- Supports the TTL sweep only.
|
|
332
|
-
CREATE INDEX IF NOT EXISTS idx_auth_challenges_expires_at
|
|
333
|
-
ON "#{schema}".auth_challenges (expires_at);
|
|
334
|
-
DDL
|
|
335
|
-
end
|
|
336
|
-
|
|
337
306
|
def configured_user_table = Kiosk.configuration.user_model.to_s.constantize.table_name
|
|
338
307
|
|
|
339
308
|
def user_id_cast(user_id_type)
|