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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +58 -0
  3. data/README.md +63 -41
  4. data/app/controllers/kiosk/server/auth_controller.rb +18 -62
  5. data/app/controllers/kiosk/server/discovery_controller.rb +25 -3
  6. data/app/controllers/kiosk/server/jwks_controller.rb +2 -2
  7. data/app/controllers/kiosk/server/kyc_attestation_controller.rb +1 -4
  8. data/app/controllers/kiosk/server/oauth_device_authorization_controller.rb +11 -29
  9. data/app/controllers/kiosk/server/oauth_registration_controller.rb +34 -0
  10. data/app/controllers/kiosk/server/oauth_token_controller.rb +33 -32
  11. data/app/models/kiosk/checkout_mandate.rb +22 -0
  12. data/app/models/kiosk/settlement.rb +1 -1
  13. data/lib/generators/kiosk/install/install_generator.rb +0 -5
  14. data/lib/generators/kiosk/install/templates/create_kiosk_mandates.rb.tt +4 -5
  15. data/lib/kiosk/payable.rb +19 -25
  16. data/lib/kiosk/server/account_binding.rb +19 -58
  17. data/lib/kiosk/server/agent_identity_providers/default_agent_idp.rb +18 -16
  18. data/lib/kiosk/server/agent_rotation.rb +62 -0
  19. data/lib/kiosk/server/assistant_key.rb +86 -0
  20. data/lib/kiosk/server/auth_toll.rb +1 -1
  21. data/lib/kiosk/server/binding_module_gate.rb +1 -1
  22. data/lib/kiosk/server/checkout.rb +57 -0
  23. data/lib/kiosk/server/client.rb +61 -0
  24. data/lib/kiosk/server/client_assertion.rb +153 -0
  25. data/lib/kiosk/server/client_registration.rb +94 -0
  26. data/lib/kiosk/server/configuration_extension.rb +56 -44
  27. data/lib/kiosk/server/device_code_grant.rb +38 -69
  28. data/lib/kiosk/server/dpop.rb +121 -0
  29. data/lib/kiosk/server/eddsa.rb +38 -0
  30. data/lib/kiosk/server/engine.rb +14 -11
  31. data/lib/kiosk/server/errors.rb +7 -0
  32. data/lib/kiosk/server/events_connection.rb +15 -5
  33. data/lib/kiosk/server/executor/repeat.rb +13 -12
  34. data/lib/kiosk/server/executor/spending_cap.rb +25 -26
  35. data/lib/kiosk/server/executor.rb +82 -101
  36. data/lib/kiosk/server/identity_resolution.rb +12 -3
  37. data/lib/kiosk/server/jwk.rb +83 -0
  38. data/lib/kiosk/server/jwks.rb +6 -0
  39. data/lib/kiosk/server/jwt_issuer.rb +7 -13
  40. data/lib/kiosk/server/kyc_verifier.rb +2 -2
  41. data/lib/kiosk/server/link_code.rb +9 -18
  42. data/lib/kiosk/server/linked_assistants.rb +1 -1
  43. data/lib/kiosk/server/mandate_verifier.rb +97 -139
  44. data/lib/kiosk/server/oauth_answers.rb +30 -0
  45. data/lib/kiosk/server/oauth_error.rb +30 -0
  46. data/lib/kiosk/server/open_api.rb +22 -20
  47. data/lib/kiosk/server/payment_claim.rb +44 -75
  48. data/lib/kiosk/server/receipts.rb +29 -0
  49. data/lib/kiosk/server/request_validation.rb +1 -2
  50. data/lib/kiosk/server/revocation_stores.rb +8 -0
  51. data/lib/kiosk/server/schema_definitions.rb +33 -64
  52. data/lib/kiosk/server/schemas/auth.schema.json +368 -57
  53. data/lib/kiosk/server/schemas/binding.schema.json +23 -58
  54. data/lib/kiosk/server/schemas/kyc.schema.json +19 -76
  55. data/lib/kiosk/server/schemas/mandates.schema.json +122 -38
  56. data/lib/kiosk/server/sd_jwt.rb +104 -0
  57. data/lib/kiosk/server/signing_key.rb +18 -52
  58. data/lib/kiosk/server/verb_wire.rb +1 -1
  59. data/lib/kiosk/server/version.rb +1 -1
  60. data/lib/kiosk/server/well_known.rb +268 -204
  61. data/lib/kiosk/server/wire_rendering.rb +1 -1
  62. data/lib/kiosk/server.rb +2 -0
  63. data/lib/kiosk/skill_link.rb +6 -2
  64. data/listen.py +38 -8
  65. metadata +26 -17
  66. data/app/models/kiosk/cart_mandate.rb +0 -21
  67. data/lib/generators/kiosk/install/templates/create_kiosk_auth_challenges.rb.tt +0 -16
  68. data/lib/kiosk/server/agent_login.rb +0 -49
  69. data/lib/kiosk/server/agent_registration.rb +0 -88
  70. data/lib/kiosk/server/auth_challenge.rb +0 -33
  71. data/lib/kiosk/server/auth_challenge_stores.rb +0 -74
  72. 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 cart against the operator's price,
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 cart line-item key naming the row, e.g. "order_id"
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(cart_mandate, payment_method: nil) = charge_row(claim!(cart_mandate), cart_mandate, payment_method)
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 cart names this row.
42
- def settlements(id) = Kiosk::Settlement.joins(:cart_mandate).merge(Kiosk::CartMandate.referencing(@reference => id))
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, cart_mandate, payment_method)
62
- settled = charge(id, cart_mandate, payment_method)
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, cart_mandate, payment_method)
69
- @psp.capture(cart_mandate, payment_method: payment_method)
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!(cart)
79
- id = referenced_id(cart)
80
- refuse_unclaimable!(id, cart.user_id.to_s) unless @row.take(id, cart.user_id.to_s, from: UNPAID, to: PAYING).positive?
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!(cart, id)
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 one row the cart's line items name, in the operator's currency.
91
- def referenced_id(cart)
92
- deny "cart currency #{cart.currency.inspect} rejected — this operator prices in #{@currency.upcase}" unless cart.currency.to_s.downcase == @currency
93
- Lines.reference(cart.line_items, @reference)
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 "cart total #{total} does not equal this operator's price #{price} for #{noun} #{id} — " \
106
- 'sign the `cart` that the action which created it returned'
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.cart_price_checker or
111
- raise Errors::ConfigurationError, "PaymentClaim needs c.cart_price_checker, the operator's price"
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 another chain. Send that chain again, " \
127
- 'byte for byte: it cannot charge twice. Do not sign a new chain. ' \
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 cart's line items: the one row they name, and the priced lines' sum.
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 = Array(line_items).filter_map { |li| li[key] }.map(&:to_s).uniq
143
- deny "cart line_items must reference exactly one #{key}" unless refs.size == 1
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
- def deny(message)
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 a chain whose capture did not complete. The row stays with
171
- # the newest unfinished chain, and the PSP keys the capture by the cart
172
- # mandate id, so the repeat cannot charge twice.
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(cart_mandate, payment_method: nil) = charge_row(reclaim!(cart_mandate), cart_mandate, payment_method)
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 this repeated chain claimed, or the row it may claim now.
179
- def reclaim!(cart)
180
- id = referenced_id(cart)
181
- payer = cart.user_id.to_s
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!(cart) if status == UNPAID
184
- return id if [PAYING, PAID].include?(status) && !settled?(id) && unfinished_chain(id, payer) == cart.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 signed id of the newest cart for this row that is neither settled nor declined.
190
- def unfinished_chain(id, payer)
191
- Kiosk::CartMandate.referencing(@reference => id).where(user_id: payer, declined_at: nil)
192
- .where.missing(:settlements).order(created_at: :desc, id: :desc).pick(:mandate_id)
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
- # `cart_mandate`, so `c.after_payment` reads the row's `settlement`. A
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(cart_mandate)
224
- id = Lines.reference(cart_mandate.line_items, @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/register' => 'auth.schema.json#/$defs/credentialRequest',
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; the signed mandate id is
130
- # `mandate_id`, unique per principal. A cart's `declined_at` records a
131
- # charge refused with no money moved. A settlement is server-minted, so it
132
- # carries no `raw_jws`.
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}".intent_mandates (
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
- mandate_id text NOT NULL,
159
- intent_mandate_id uuid NOT NULL REFERENCES "#{schema}".intent_mandates(id) ON DELETE CASCADE,
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
- raw_jws text NOT NULL,
151
+ sd_jwt text NOT NULL,
169
152
  declined_at timestamptz,
170
- UNIQUE (user_id, mandate_id)
153
+ UNIQUE (user_id, reference)
171
154
  );
172
- CREATE INDEX IF NOT EXISTS idx_cart_mandates_user_id ON "#{schema}".cart_mandates (user_id);
173
- CREATE INDEX IF NOT EXISTS idx_cart_mandates_intent ON "#{schema}".cart_mandates (intent_mandate_id);
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 uuid PRIMARY KEY DEFAULT gen_random_uuid(),
177
- mandate_id text NOT NULL,
178
- cart_mandate_id uuid NOT NULL REFERENCES "#{schema}".cart_mandates(id) ON DELETE CASCADE,
179
- user_id #{col_type} NOT NULL,
180
- agent_id uuid NOT NULL,
181
- issuer text NOT NULL,
182
- payment_method text NOT NULL,
183
- amount_cents bigint NOT NULL,
184
- currency text NOT NULL,
185
- expires_at timestamptz,
186
- created_at timestamptz NOT NULL DEFAULT now(),
187
- raw_jws text NOT NULL,
188
- UNIQUE (user_id, mandate_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 ON "#{schema}".payment_mandates (user_id);
191
- CREATE INDEX IF NOT EXISTS idx_payment_mandates_cart ON "#{schema}".payment_mandates (cart_mandate_id);
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
- cart_mandate_id uuid NOT NULL REFERENCES "#{schema}".cart_mandates(id) ON DELETE CASCADE,
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 (cart_mandate_id)
188
+ UNIQUE (checkout_mandate_id)
204
189
  );
205
- CREATE INDEX IF NOT EXISTS idx_settlements_user_id ON "#{schema}".settlements (user_id);
206
- CREATE INDEX IF NOT EXISTS idx_settlements_cart ON "#{schema}".settlements (cart_mandate_id);
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 challenge ids; the primary key is the single-use gate.
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)