kiosk-pay-stripe 0.5.20
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/CHANGELOG.md +126 -0
- data/LICENSE.txt +202 -0
- data/README.md +95 -0
- data/lib/generators/kiosk/pay_stripe/install/install_generator.rb +31 -0
- data/lib/generators/kiosk/pay_stripe/install/templates/create_kiosk_pay_stripe_customers.rb.tt +13 -0
- data/lib/kiosk/payment_providers/stripe/application_record.rb +14 -0
- data/lib/kiosk/payment_providers/stripe/charges.rb +61 -0
- data/lib/kiosk/payment_providers/stripe/customer_record.rb +22 -0
- data/lib/kiosk/payment_providers/stripe/customers.rb +45 -0
- data/lib/kiosk/payment_providers/stripe/setup_sessions.rb +47 -0
- data/lib/kiosk/payment_providers/stripe/version.rb +9 -0
- data/lib/kiosk/payment_providers/stripe.rb +107 -0
- data/lib/kiosk-pay-stripe.rb +6 -0
- metadata +123 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: fec149e6c2424f7f54664ab1f78b58e0a6637c86157b132f86c11632f26043c7
|
|
4
|
+
data.tar.gz: 2b8e667195f02a284129d8a03fafc4bf72d75229a7cc979092c8138f98fe4691
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c346ef027f8940c50a8141513e2ba18b5d88503083ba066fd5aa18c7d8e5e90b31c4def78ef94fe7c5df42bf8000c23ec9bf19a4edf80d93dff0f888a33eedb0
|
|
7
|
+
data.tar.gz: 2c362b0f9605864b32fb1a0a89a359f003b5340e478f8c3d7743208f6ff046fb4aaf9fa68ef096a96ea4ee3b645be09abbbdc40c47ee3788403489e041401a27
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
How to write an entry, and what a release section means: `CHANGELOG-RULE.md` at
|
|
4
|
+
the root of this repository —
|
|
5
|
+
<https://github.com/kiosk-hq/kiosk/blob/main/CHANGELOG-RULE.md>. In short: under
|
|
6
|
+
200 characters, one or two sentences, the essence rather than the content; write it
|
|
7
|
+
under `## [Unreleased]`; a cut renames that heading to
|
|
8
|
+
`## [MAJOR.MINOR.PATCH] — <date>` and opens a fresh empty one above it; nothing
|
|
9
|
+
already written is edited.
|
|
10
|
+
|
|
11
|
+
## [Unreleased]
|
|
12
|
+
|
|
13
|
+
## [0.5.20] — 2026-10-20
|
|
14
|
+
|
|
15
|
+
- 2026-10-20: Version 0.5.20, the tree cut that matches skill 0.5.20.
|
|
16
|
+
|
|
17
|
+
## [0.5.19] — 2026-10-20
|
|
18
|
+
|
|
19
|
+
- 2026-10-20: Version 0.5.19, the tree cut that matches skill 0.5.19.
|
|
20
|
+
|
|
21
|
+
## [0.5.18] — 2026-10-18
|
|
22
|
+
|
|
23
|
+
- 2026-10-18: Version 0.5.18, the tree cut that matches skill 0.5.18.
|
|
24
|
+
|
|
25
|
+
## [0.5.17] — 2026-10-18
|
|
26
|
+
|
|
27
|
+
- 2026-10-18: kiosk-pay-stripe keys a capture by the Stripe customer and the cart mandate id, so the carts of two customers cannot share an idempotency key.
|
|
28
|
+
- 2026-10-18: `refund` takes `key:`, which names the refund among the refunds of its charge; kiosk-pay-stripe keys the Stripe request by the charge and that key.
|
|
29
|
+
- 2026-10-18: `PaymentFailed` takes no `reason:`; the engine reads only its message and `retryable?`.
|
|
30
|
+
|
|
31
|
+
## [0.5.16] — 2026-10-18
|
|
32
|
+
|
|
33
|
+
- 2026-10-18: Each adapter has its own Stripe client; `api_base:` names another API URL, e.g. stripe-mock. The adapter no longer sets `Stripe.api_key`.
|
|
34
|
+
- 2026-10-18: A saved-card check is one Stripe call: the newest card on the Customer. A `pay` makes three calls, not five.
|
|
35
|
+
|
|
36
|
+
## [0.5.15] — 2026-10-18
|
|
37
|
+
|
|
38
|
+
- 2026-10-18: `bin/rails g kiosk:pay_stripe:install` writes the `stripe_customers` migration with its DDL in the file. `CustomerRecord.create_table` is removed.
|
|
39
|
+
|
|
40
|
+
## [0.5.14] — 2026-10-18
|
|
41
|
+
|
|
42
|
+
- 2026-10-18: Version 0.5.14, the tree cut that matches skill 0.5.14.
|
|
43
|
+
|
|
44
|
+
- 2026-10-17: **`test_autocard:` and `attach_test_card` are removed**; a suite saves a card through `payment_setup`, as production does.
|
|
45
|
+
|
|
46
|
+
- 2026-10-17: `Stripe::ChargeLookup` is removed; nothing an operator runs used it.
|
|
47
|
+
|
|
48
|
+
## [0.5.13] — 2026-10-11
|
|
49
|
+
|
|
50
|
+
- 2026-10-11: Version 0.5.13, the tree cut that matches skill 0.5.13.
|
|
51
|
+
|
|
52
|
+
- 2026-10-09: **`Stripe.new` requires `api_key:`**; the adapter no longer falls back to `STRIPE_SECRET_KEY`.
|
|
53
|
+
|
|
54
|
+
## [0.5.12] — 2026-10-08
|
|
55
|
+
|
|
56
|
+
- 2026-10-08: Version 0.5.12, the tree cut that matches skill 0.5.12; this gem's surface is unchanged.
|
|
57
|
+
|
|
58
|
+
## [0.5.11] — 2026-10-08
|
|
59
|
+
|
|
60
|
+
- 2026-10-08: Version 0.5.11, the tree cut that matches skill 0.5.11; this gem's surface is unchanged.
|
|
61
|
+
|
|
62
|
+
## [0.5.10] — 2026-10-08
|
|
63
|
+
|
|
64
|
+
- 2026-10-08: Version 0.5.10, the tree cut that matches skill 0.5.10 and carries the entries below.
|
|
65
|
+
|
|
66
|
+
- 2026-10-08: A capture settles only a `succeeded` card PaymentIntent, at what it received; any other status is refused as a decline or an unknown outcome.
|
|
67
|
+
- 2026-10-08: A capture settles the confirmed intent's amount, the cart total the spec requires, rather than `amount_received`.
|
|
68
|
+
- 2026-10-08: `CustomerRecord` keeps the principal→Customer mapping in a `stripe_customers` table and is the adapter's default, so an operator writes no mapping of its own.
|
|
69
|
+
- 2026-10-08: `refund` reverses a capture by its PaymentIntent, so an operator can give the money back to the card it came from.
|
|
70
|
+
- 2026-10-08: `Stripe::ChargeLookup` answers whether a cart mandate was charged, by the metadata the adapter stamps, for reconciling a capture the operator cannot account for.
|
|
71
|
+
|
|
72
|
+
## [0.5.9] — 2026-10-08
|
|
73
|
+
|
|
74
|
+
- 2026-10-08: Version 0.5.9, the tree cut that matches skill 0.5.9; this gem's surface is unchanged.
|
|
75
|
+
|
|
76
|
+
## [0.5.8] — 2026-10-07
|
|
77
|
+
|
|
78
|
+
- 2026-10-07: Version 0.5.8, the tree cut that matches skill 0.5.8; this gem's surface is unchanged.
|
|
79
|
+
|
|
80
|
+
## [0.5.7] — 2026-10-07
|
|
81
|
+
|
|
82
|
+
- 2026-10-07: Version 0.5.7, the tree cut that matches skill 0.5.7; this gem's surface is unchanged.
|
|
83
|
+
|
|
84
|
+
## [0.5.6] — 2026-10-07
|
|
85
|
+
|
|
86
|
+
- 2026-10-07: Version 0.5.6, the tree cut that matches skill 0.5.6; this gem's surface is unchanged.
|
|
87
|
+
|
|
88
|
+
## [0.5.5] — 2026-10-07
|
|
89
|
+
|
|
90
|
+
- 2026-10-07: Version 0.5.5, the tree cut that matches skill 0.5.5.
|
|
91
|
+
|
|
92
|
+
- 2026-10-07: **`customer_resolver:` and `customer_saver:` are required**; the mode that charged a presented or `test_payment_method` card without them is gone (K-1994).
|
|
93
|
+
- 2026-10-07: **A mapped Customer that Stripe no longer has counts as no saved card**; `setup_url` saves a fresh one through `customer_saver`, which now replaces the mapping (K-1993).
|
|
94
|
+
## [0.5.4] — 2026-10-07
|
|
95
|
+
|
|
96
|
+
- 2026-10-07: **`setup_url` takes the engine's `return_url`, and `setup_return_user_id` names whose human came back**, so the return page is kiosk-server's; the `return_url:` option is gone (T-219).
|
|
97
|
+
|
|
98
|
+
## [0.5.3] — 2026-10-06
|
|
99
|
+
|
|
100
|
+
- 2026-10-06: Version 0.5.3, the tree cut that matches skill 0.5.3; this gem's surface is unchanged.
|
|
101
|
+
|
|
102
|
+
## [0.5.2] — 2026-10-06
|
|
103
|
+
|
|
104
|
+
- 2026-10-05: **A `return_url:` path is joined to the origin being served**, so on a deployment serving several origins the human returns to the one they came from.
|
|
105
|
+
|
|
106
|
+
- 2026-10-05: **The derived return URL names the origin being served** (`Kiosk.current_issuer`), not only the default issuer.
|
|
107
|
+
|
|
108
|
+
## [0.5.0] — 2026-09-26
|
|
109
|
+
- **The rubygems blurb stopped forecasting PSP adapters that do not exist (K-1673).** It names the base class a host subclasses instead.
|
|
110
|
+
- **The rubygems blurb credited this gem's charging to a `kiosk-core` method that only raises (K-1688).** It names this adapter's own `#capture` now.
|
|
111
|
+
- **`return_url:` — the keyword whose absence raises — is documented, and three stale or inverted comments are gone (K-1690, K-1691, K-1692).**
|
|
112
|
+
- The two operator log lines that report a degraded `setup_url` now end in the
|
|
113
|
+
condition itself rather than in an internal tracker id nobody outside this
|
|
114
|
+
project can look up.
|
|
115
|
+
- A failed lookup for the outstanding setup session is now logged instead of
|
|
116
|
+
passing for "there is none": the adapter still degrades to minting a fresh
|
|
117
|
+
session so the readiness probe keeps answering, but it says so, because the
|
|
118
|
+
degrade is otherwise byte-for-byte identical to the stable-url happy path.
|
|
119
|
+
- `setup_url` is now stable across calls (K-492): it reuses the `mode:setup`
|
|
120
|
+
Checkout Session already outstanding for the customer instead of creating a
|
|
121
|
+
new one per call, so a host polling card-setup readiness keeps handing its
|
|
122
|
+
human the same link.
|
|
123
|
+
- Initial Stripe PSP adapter (test mode): SetupIntent card-on-file with
|
|
124
|
+
off_session capture (`customer_resolver`/`customer_saver`, `setup_url`,
|
|
125
|
+
`setup_required?`, `saved_method?`), plus a `pm_card_visa` back-compat
|
|
126
|
+
fallback and a `test_autocard`/`attach_test_card` path for automated suites.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
data/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# kiosk-pay-stripe
|
|
2
|
+
|
|
3
|
+
Stripe PSP adapter for [Kiosk](https://kiosk.tech). Implements
|
|
4
|
+
`Kiosk::PaymentProviders::Base` over Stripe PaymentIntents.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
> **Not on RubyGems yet** — so every `gem` line below carries `github: "kiosk-hq/kiosk"`, which is what makes it copy-pasteable today. Publication status and the canonical install are stated once, in the monorepo README's [Install](https://github.com/kiosk-hq/kiosk#install) section.
|
|
9
|
+
|
|
10
|
+
There is no default PSP — `Kiosk::Configuration#payment_provider` starts `nil`
|
|
11
|
+
— and neither `kiosk-server` nor `kiosk-all` pulls an adapter in, so taking
|
|
12
|
+
payments means adding this one yourself:
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
gem "kiosk-pay-stripe", github: "kiosk-hq/kiosk"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Bundler brings the three runtime dependencies with it: `activerecord`,
|
|
19
|
+
`kiosk-core` and Stripe's own `stripe` gem. In a Rails app `Bundler.require`
|
|
20
|
+
loads the adapter. The adapter does nothing until you assign it to
|
|
21
|
+
`payment_provider`, as the block below shows.
|
|
22
|
+
|
|
23
|
+
You also need a secret key for the **provider's own** Stripe account
|
|
24
|
+
(`sk_test_…` for the PoC; test mode only, see below). The buyer's card is
|
|
25
|
+
saved on that account, so there is no Kiosk-side Stripe account to configure.
|
|
26
|
+
|
|
27
|
+
## Model: card-on-file + off_session (SetupIntent)
|
|
28
|
+
|
|
29
|
+
The headline flow saves the buyer's card ONCE on the *provider's* Stripe
|
|
30
|
+
account as a Customer + PaymentMethod (keyed to the synthetic principal /
|
|
31
|
+
`user_id`). Subsequent purchases are charged `off_session`
|
|
32
|
+
(merchant-initiated) against that saved card — the assistant authorizes,
|
|
33
|
+
never presents a card.
|
|
34
|
+
|
|
35
|
+
The `principal → Stripe Customer` mapping lives in the host's `stripe_customers`
|
|
36
|
+
table, through `Kiosk::PaymentProviders::Stripe::CustomerRecord`. The install
|
|
37
|
+
generator writes the migration that creates this table. Give it the same
|
|
38
|
+
`--user-id-type` that you gave `kiosk:install`:
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
bin/rails generate kiosk:pay_stripe:install --user-id-type=uuid
|
|
42
|
+
bin/rails db:migrate
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
require "kiosk/payment_providers/stripe"
|
|
47
|
+
|
|
48
|
+
Kiosk.configure do |c|
|
|
49
|
+
c.payment_provider = Kiosk::PaymentProviders::Stripe.new(
|
|
50
|
+
api_key: ENV["STRIPE_SECRET_KEY"], # sk_test_… for the PoC
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
An app that already keeps the mapping elsewhere passes `customer_resolver:
|
|
56
|
+
->(user_id) { … }` and `customer_saver: ->(user_id, cus_id) { … }` instead.
|
|
57
|
+
|
|
58
|
+
The saver replaces any earlier mapping for the principal: when Stripe no
|
|
59
|
+
longer has the mapped Customer (deleted, or from another account), the adapter
|
|
60
|
+
treats the principal as having no saved card and saves a fresh Customer.
|
|
61
|
+
|
|
62
|
+
Card-setup handshake (see the Payment section of the spec):
|
|
63
|
+
|
|
64
|
+
- `setup_required?(user_id:)` — true when the principal must set up a card
|
|
65
|
+
before a charge can proceed.
|
|
66
|
+
- `setup_url(user_id:, return_url:)` — hosted Stripe Checkout URL (`mode: "setup"`). The
|
|
67
|
+
human opens it in a browser (NOT the chat) to enter their card on Stripe's
|
|
68
|
+
hosted page; Stripe then redirects them to kiosk-server's
|
|
69
|
+
`<mount>/payment_setup/return` page, which kiosk-server passes as `return_url`. The gem
|
|
70
|
+
never sees card data. **Stable across polls:** an already-outstanding
|
|
71
|
+
(`status: open`, same `return_url`) setup session is reused, so an assistant
|
|
72
|
+
polling readiness keeps handing its human the SAME link instead of a fresh
|
|
73
|
+
one per poll. One exception, and it is logged rather than hidden: if the
|
|
74
|
+
lookup itself errors, the adapter cannot tell "no outstanding session" from
|
|
75
|
+
"did not find out", so it mints a fresh session (the probe keeps answering)
|
|
76
|
+
and warns that the url is not stable until that clears.
|
|
77
|
+
- `setup_return_user_id(params)` — the principal whose Checkout Session the
|
|
78
|
+
return page's `session_id` names, asked of Stripe; kiosk-server then pushes the
|
|
79
|
+
`payment_setup` event once `setup_required?` answers false.
|
|
80
|
+
- `saved_method?(user_id:)` — true once the resolved Customer has a usable
|
|
81
|
+
saved card.
|
|
82
|
+
- `refund(psp_reference:, amount_cents:, key:)` — reverses a capture by its
|
|
83
|
+
PaymentIntent, back to the card it came from; answers the `re_…` reference.
|
|
84
|
+
The Stripe request is keyed by the charge and `key`, so each refund of one
|
|
85
|
+
charge is its own, and a retry with the same key refunds once.
|
|
86
|
+
|
|
87
|
+
Test mode only for the PoC (`sk_test_…`).
|
|
88
|
+
|
|
89
|
+
### Test mode
|
|
90
|
+
|
|
91
|
+
Automated suites point the Stripe SDK at `stripe-mock`
|
|
92
|
+
(`Kiosk::TestHelpers::StripeMock` in `kiosk-test-support`) and take the same
|
|
93
|
+
path as production: the assistant calls `payment_setup`, which creates and maps
|
|
94
|
+
the Customer. stripe-mock lists a saved card for every Customer, so a later
|
|
95
|
+
`pay` charges it.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
require 'rails/generators/active_record'
|
|
5
|
+
|
|
6
|
+
module Kiosk
|
|
7
|
+
module PayStripe
|
|
8
|
+
module Generators
|
|
9
|
+
# `bin/rails g kiosk:pay_stripe:install`: writes the migration that creates
|
|
10
|
+
# the `stripe_customers` table, with its DDL written into the file.
|
|
11
|
+
# {Kiosk::PaymentProviders::Stripe::CustomerRecord} reads and writes that table.
|
|
12
|
+
class InstallGenerator < ::Rails::Generators::Base
|
|
13
|
+
include ::Rails::Generators::Migration
|
|
14
|
+
|
|
15
|
+
source_root File.expand_path('templates', __dir__)
|
|
16
|
+
|
|
17
|
+
desc 'Generate the migration for the stripe_customers table of kiosk-pay-stripe.'
|
|
18
|
+
|
|
19
|
+
class_option :user_id_type, type: :string, default: 'uuid', enum: %w[uuid bigint integer text],
|
|
20
|
+
desc: 'User-id column type: give the same type as to kiosk:install'
|
|
21
|
+
|
|
22
|
+
def self.next_migration_number(dirname) = ::ActiveRecord::Generators::Base.next_migration_number(dirname)
|
|
23
|
+
|
|
24
|
+
def create_stripe_customers_migration
|
|
25
|
+
migration_template 'create_kiosk_pay_stripe_customers.rb.tt',
|
|
26
|
+
'db/migrate/create_kiosk_pay_stripe_customers.rb'
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/generators/kiosk/pay_stripe/install/templates/create_kiosk_pay_stripe_customers.rb.tt
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The principal → Stripe Customer mapping kiosk-pay-stripe reads and writes.
|
|
4
|
+
class CreateKioskPayStripeCustomers < ActiveRecord::Migration[ActiveRecord::Migration.current_version]
|
|
5
|
+
def change
|
|
6
|
+
create_table :stripe_customers do |t|
|
|
7
|
+
t.column :user_id, :<%= options[:user_id_type] %>, null: false
|
|
8
|
+
t.string :customer_id, null: false
|
|
9
|
+
t.timestamps
|
|
10
|
+
end
|
|
11
|
+
add_index :stripe_customers, :user_id, unique: true
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_record'
|
|
4
|
+
|
|
5
|
+
module Kiosk
|
|
6
|
+
module PaymentProviders
|
|
7
|
+
class Stripe < Base
|
|
8
|
+
# The base of the adapter's models.
|
|
9
|
+
class ApplicationRecord < ::ActiveRecord::Base
|
|
10
|
+
self.abstract_class = true
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kiosk
|
|
4
|
+
module PaymentProviders
|
|
5
|
+
class Stripe < Base
|
|
6
|
+
# Creates the off_session PaymentIntent and maps its outcome to PaymentFailed.
|
|
7
|
+
module Charges
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def charge(cart_mandate, customer_id, payment_method)
|
|
11
|
+
@stripe.payment_intents.create(
|
|
12
|
+
intent_params(cart_mandate, customer_id, payment_method),
|
|
13
|
+
{ idempotency_key: "#{customer_id}-#{cart_mandate.id}-capture" }
|
|
14
|
+
)
|
|
15
|
+
rescue ::Stripe::CardError => e
|
|
16
|
+
# A definitive decline: nothing was charged, so a retry is safe.
|
|
17
|
+
raise PaymentFailed.new(card_decline_message(e), retryable: true)
|
|
18
|
+
rescue ::Stripe::StripeError
|
|
19
|
+
# The outcome is unknown: a blind retry could double-charge.
|
|
20
|
+
raise unknown_outcome('could not confirm the charge')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def intent_params(cart_mandate, customer_id, payment_method)
|
|
24
|
+
{
|
|
25
|
+
amount: cart_mandate.total_amount_cents,
|
|
26
|
+
currency: cart_mandate.currency,
|
|
27
|
+
customer: customer_id,
|
|
28
|
+
payment_method: payment_method,
|
|
29
|
+
payment_method_types: ['card'],
|
|
30
|
+
off_session: true,
|
|
31
|
+
confirm: true,
|
|
32
|
+
metadata: { cart_mandate_id: cart_mandate.id }
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def ensure_paid!(intent)
|
|
37
|
+
return if intent.status == PAID
|
|
38
|
+
|
|
39
|
+
raise PaymentFailed.new('the payment method was declined', retryable: true) if
|
|
40
|
+
NOT_CHARGED.include?(intent.status)
|
|
41
|
+
|
|
42
|
+
raise unknown_outcome('has not completed the charge')
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def unknown_outcome(what)
|
|
46
|
+
PaymentFailed.new("the payment processor #{what}; its status is unknown", retryable: false)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Keyed on Stripe's stable error code: Stripe's own message can carry request ids.
|
|
50
|
+
def card_decline_message(error)
|
|
51
|
+
case error.respond_to?(:code) ? error.code : nil
|
|
52
|
+
when 'expired_card' then 'the payment method has expired'
|
|
53
|
+
when 'insufficient_funds' then 'the payment method has insufficient funds'
|
|
54
|
+
when 'authentication_required' then 'the payment method needs authentication an off-session charge cannot complete'
|
|
55
|
+
else 'the payment method was declined'
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'kiosk/payment_providers/stripe/application_record'
|
|
4
|
+
|
|
5
|
+
module Kiosk
|
|
6
|
+
module PaymentProviders
|
|
7
|
+
class Stripe < Base
|
|
8
|
+
# The principal → Stripe Customer mapping, one row per principal, in the
|
|
9
|
+
# host's `stripe_customers` table (`bin/rails g kiosk:pay_stripe:install`).
|
|
10
|
+
# The adapter's default resolver and saver.
|
|
11
|
+
class CustomerRecord < ApplicationRecord
|
|
12
|
+
self.table_name = 'stripe_customers'
|
|
13
|
+
|
|
14
|
+
def self.resolve(user_id) = find_by(user_id: user_id)&.customer_id
|
|
15
|
+
|
|
16
|
+
def self.save(user_id, customer_id)
|
|
17
|
+
find_or_initialize_by(user_id: user_id).update!(customer_id: customer_id)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kiosk
|
|
4
|
+
module PaymentProviders
|
|
5
|
+
class Stripe < Base
|
|
6
|
+
# Maps a principal to its Stripe Customer and the Customer's saved card.
|
|
7
|
+
module Customers
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def ensure_customer(user_id)
|
|
11
|
+
existing = live_customer(user_id)
|
|
12
|
+
return existing.id if existing
|
|
13
|
+
|
|
14
|
+
cus = @stripe.customers.create({ name: "principal-#{user_id}" })
|
|
15
|
+
@customer_saver.call(user_id, cus.id)
|
|
16
|
+
cus.id
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# nil when none is mapped or Stripe no longer has the mapped one.
|
|
20
|
+
def live_customer(user_id)
|
|
21
|
+
cus_id = @customer_resolver.call(user_id)
|
|
22
|
+
return nil unless cus_id
|
|
23
|
+
|
|
24
|
+
customer = @stripe.customers.retrieve(cus_id)
|
|
25
|
+
customer unless customer.respond_to?(:deleted) && customer.deleted
|
|
26
|
+
rescue ::Stripe::InvalidRequestError => e
|
|
27
|
+
raise unless e.code == 'resource_missing'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# `[customer_id, card_id]` for the newest card on the mapped Customer, or
|
|
31
|
+
# nil. One Stripe call: a deleted Customer keeps no card, and a Customer
|
|
32
|
+
# Stripe does not know is no card.
|
|
33
|
+
def saved_card(user_id)
|
|
34
|
+
cus_id = @customer_resolver.call(user_id)
|
|
35
|
+
return nil unless cus_id
|
|
36
|
+
|
|
37
|
+
card = @stripe.payment_methods.list(customer: cus_id, type: 'card', limit: 1).data.first
|
|
38
|
+
[cus_id, card.id] if card
|
|
39
|
+
rescue ::Stripe::InvalidRequestError => e
|
|
40
|
+
raise unless e.code == 'resource_missing'
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kiosk
|
|
4
|
+
module PaymentProviders
|
|
5
|
+
class Stripe < Base
|
|
6
|
+
# Finds an open setup Checkout Session that `setup_url` can hand out again.
|
|
7
|
+
module SetupSessions
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def outstanding_setup_session(cus_id, success_url:)
|
|
11
|
+
open_sessions = Array(
|
|
12
|
+
@stripe.checkout.sessions.list(customer: cus_id, status: 'open', limit: SETUP_SESSION_LIST_LIMIT)&.data
|
|
13
|
+
)
|
|
14
|
+
match = open_sessions.find { |s| reusable_session?(s, success_url) }
|
|
15
|
+
warn_full_page(open_sessions.size) if match.nil? && open_sessions.size >= SETUP_SESSION_LIST_LIMIT
|
|
16
|
+
match
|
|
17
|
+
rescue ::Stripe::StripeError => e
|
|
18
|
+
log_warning("could not list open setup sessions (#{e.class}: #{e.message}); minting a " \
|
|
19
|
+
'fresh one, so setup_url changes between polls until this clears')
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def reusable_session?(session, success_url)
|
|
24
|
+
field(session, :mode) == 'setup' &&
|
|
25
|
+
field(session, :success_url) == success_url &&
|
|
26
|
+
!field(session, :url).to_s.empty?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def warn_full_page(size)
|
|
30
|
+
log_warning("no reusable setup session among a full page of #{size} open " \
|
|
31
|
+
'Checkout Sessions; minting a fresh one, so setup_url may change between polls')
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def log_warning(text)
|
|
35
|
+
message = "[kiosk-pay-stripe] #{text}"
|
|
36
|
+
logger = ::Rails.logger if defined?(::Rails) && ::Rails.respond_to?(:logger)
|
|
37
|
+
logger ? logger.warn(message) : warn(message)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# The SDK's StripeObject raises NoMethodError for fields the API omitted.
|
|
41
|
+
def field(obj, name)
|
|
42
|
+
obj.respond_to?(name) ? obj.public_send(name) : nil
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'kiosk'
|
|
4
|
+
require 'kiosk/payment_providers/stripe/version'
|
|
5
|
+
require 'kiosk/payment_providers/stripe/charges'
|
|
6
|
+
require 'kiosk/payment_providers/stripe/customers'
|
|
7
|
+
require 'kiosk/payment_providers/stripe/setup_sessions'
|
|
8
|
+
|
|
9
|
+
module Kiosk
|
|
10
|
+
module PaymentProviders
|
|
11
|
+
# Stripe PSP adapter: the human saves a card once on the operator's Stripe
|
|
12
|
+
# account, and purchases are charged `off_session`.
|
|
13
|
+
# Always reference the SDK as `::Stripe` — bare `Stripe` is this class.
|
|
14
|
+
class Stripe < Base
|
|
15
|
+
include Charges
|
|
16
|
+
include Customers
|
|
17
|
+
include SetupSessions
|
|
18
|
+
|
|
19
|
+
VERSION = StripeVersion::VERSION
|
|
20
|
+
|
|
21
|
+
# Open Checkout Sessions the reuse lookup asks for. A full page with no
|
|
22
|
+
# match is logged: the reusable session may have been truncated away.
|
|
23
|
+
SETUP_SESSION_LIST_LIMIT = 10
|
|
24
|
+
|
|
25
|
+
# Stripe substitutes the session id on the redirect.
|
|
26
|
+
RETURN_QUERY = 'session_id={CHECKOUT_SESSION_ID}'
|
|
27
|
+
|
|
28
|
+
PAID = 'succeeded'
|
|
29
|
+
# Terminal PaymentIntent states in which no money moved.
|
|
30
|
+
NOT_CHARGED = %w[canceled requires_payment_method].freeze
|
|
31
|
+
|
|
32
|
+
# @param customer_resolver [#call] `(user_id) -> customer_id | nil`; {CustomerRecord.resolve} when omitted
|
|
33
|
+
# @param customer_saver [#call] `(user_id, customer_id)`; {CustomerRecord.save} when omitted
|
|
34
|
+
# @param api_base [String, nil] the Stripe API URL, e.g. a local stripe-mock; Stripe's own when nil
|
|
35
|
+
def initialize(api_key:, customer_resolver: nil, customer_saver: nil, api_base: nil)
|
|
36
|
+
super()
|
|
37
|
+
@customer_resolver = customer_resolver || customer_record_method(:resolve)
|
|
38
|
+
@customer_saver = customer_saver || customer_record_method(:save)
|
|
39
|
+
require 'stripe'
|
|
40
|
+
# This adapter's own client: the key and the URL are not process-global.
|
|
41
|
+
@stripe = ::Stripe::StripeClient.new(api_key, api_base:).v1
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# A hosted Checkout page in `mode: "setup"`. An open setup session for the
|
|
45
|
+
# same return target is reused, so an assistant polling `payment_setup`
|
|
46
|
+
# keeps handing its human the same link. The SetupIntent the page creates
|
|
47
|
+
# takes Stripe's default `usage`, `off_session`, so `pay` can charge the card.
|
|
48
|
+
def setup_url(user_id:, return_url:)
|
|
49
|
+
success_url = "#{return_url}?#{RETURN_QUERY}"
|
|
50
|
+
cus_id = ensure_customer(user_id)
|
|
51
|
+
|
|
52
|
+
outstanding_setup_session(cus_id, success_url: success_url)&.url ||
|
|
53
|
+
@stripe.checkout.sessions.create(
|
|
54
|
+
mode: 'setup',
|
|
55
|
+
customer: cus_id,
|
|
56
|
+
client_reference_id: user_id,
|
|
57
|
+
payment_method_types: ['card'],
|
|
58
|
+
success_url: success_url
|
|
59
|
+
).url
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# The principal the returning browser's Checkout Session was minted for.
|
|
63
|
+
def setup_return_user_id(params)
|
|
64
|
+
session_id = params['session_id'].to_s
|
|
65
|
+
return nil if session_id.empty?
|
|
66
|
+
|
|
67
|
+
@stripe.checkout.sessions.retrieve(session_id).client_reference_id
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def setup_required?(user_id:) = !saved_method?(user_id: user_id)
|
|
71
|
+
|
|
72
|
+
def saved_method?(user_id:) = !saved_card(user_id).nil?
|
|
73
|
+
|
|
74
|
+
# Charges the principal's saved card off_session; the mandate's payment method is not used.
|
|
75
|
+
def capture(cart_mandate, **)
|
|
76
|
+
customer_id, payment_method = saved_card(cart_mandate.user_id)
|
|
77
|
+
raise SetupRequired unless payment_method
|
|
78
|
+
|
|
79
|
+
intent = charge(cart_mandate, customer_id, payment_method)
|
|
80
|
+
ensure_paid!(intent)
|
|
81
|
+
{
|
|
82
|
+
psp_reference: intent.id,
|
|
83
|
+
settled_amount_cents: intent.amount_received,
|
|
84
|
+
settled_at: Time.at(intent.created).utc
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def refund(psp_reference:, amount_cents:, key:)
|
|
89
|
+
refund = @stripe.refunds.create(
|
|
90
|
+
{ payment_intent: psp_reference, amount: amount_cents },
|
|
91
|
+
{ idempotency_key: "#{psp_reference}-refund-#{key}" }
|
|
92
|
+
)
|
|
93
|
+
{ psp_reference: refund.id, refunded_psp_reference: psp_reference,
|
|
94
|
+
refunded_amount_cents: amount_cents, refunded_at: Time.at(refund.created).utc }
|
|
95
|
+
rescue ::Stripe::StripeError
|
|
96
|
+
raise PaymentFailed, 'the payment processor did not refund the charge'
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def customer_record_method(name)
|
|
102
|
+
require 'kiosk/payment_providers/stripe/customer_record'
|
|
103
|
+
CustomerRecord.method(name)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# rubocop:disable Naming/FileName -- Bundler.require loads the file named after the gem.
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Convenience entry — `require "kiosk-pay-stripe"` mirrors the gem name.
|
|
5
|
+
require 'kiosk/payment_providers/stripe'
|
|
6
|
+
# rubocop:enable Naming/FileName
|
metadata
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: kiosk-pay-stripe
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.5.20
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Phil Pirozhkov
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activerecord
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '8.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '8.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: kiosk-core
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 0.5.0
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 0.5.0
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: stripe
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '19'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '19'
|
|
54
|
+
description: |
|
|
55
|
+
kiosk-pay-stripe is the open-source Stripe payment adapter for Kiosk. It
|
|
56
|
+
implements both halves of the provider-acquired, card-on-file model:
|
|
57
|
+
|
|
58
|
+
- Card acquisition. `setup_url(user_id:, return_url:)` returns a hosted Stripe
|
|
59
|
+
Checkout session in `mode: "setup"` that saves the buyer's card on the
|
|
60
|
+
PROVIDER's own Stripe account (an existing open setup session is
|
|
61
|
+
reused, so the url is stable across polls); `setup_required?` tells the
|
|
62
|
+
wire whether a principal still has to complete it, backed by
|
|
63
|
+
`saved_method?`, which asks Stripe whether the resolved Customer has a
|
|
64
|
+
usable card.
|
|
65
|
+
- Charging. This adapter's own `#capture` settles an AP2 cart
|
|
66
|
+
mandate as an `off_session` (merchant-initiated) PaymentIntent against
|
|
67
|
+
that saved card — the assistant authorizes the cart, it never presents
|
|
68
|
+
a card.
|
|
69
|
+
|
|
70
|
+
The gem stays app-agnostic: the principal-to-Stripe-Customer mapping is
|
|
71
|
+
injected by the host through `customer_resolver:` / `customer_saver:`
|
|
72
|
+
callables, and no application table is read inside the gem.
|
|
73
|
+
|
|
74
|
+
Today only kiosk-pay-stripe ships; there is no default PSP
|
|
75
|
+
(Configuration#payment_provider is nil). A host that needs another PSP
|
|
76
|
+
subclasses Kiosk::PaymentProviders::Base and assigns the instance to
|
|
77
|
+
Kiosk.configuration.payment_provider.
|
|
78
|
+
email:
|
|
79
|
+
- hello@fili.pp.ru
|
|
80
|
+
executables: []
|
|
81
|
+
extensions: []
|
|
82
|
+
extra_rdoc_files: []
|
|
83
|
+
files:
|
|
84
|
+
- CHANGELOG.md
|
|
85
|
+
- LICENSE.txt
|
|
86
|
+
- README.md
|
|
87
|
+
- lib/generators/kiosk/pay_stripe/install/install_generator.rb
|
|
88
|
+
- lib/generators/kiosk/pay_stripe/install/templates/create_kiosk_pay_stripe_customers.rb.tt
|
|
89
|
+
- lib/kiosk-pay-stripe.rb
|
|
90
|
+
- lib/kiosk/payment_providers/stripe.rb
|
|
91
|
+
- lib/kiosk/payment_providers/stripe/application_record.rb
|
|
92
|
+
- lib/kiosk/payment_providers/stripe/charges.rb
|
|
93
|
+
- lib/kiosk/payment_providers/stripe/customer_record.rb
|
|
94
|
+
- lib/kiosk/payment_providers/stripe/customers.rb
|
|
95
|
+
- lib/kiosk/payment_providers/stripe/setup_sessions.rb
|
|
96
|
+
- lib/kiosk/payment_providers/stripe/version.rb
|
|
97
|
+
homepage: https://kiosk.tech
|
|
98
|
+
licenses:
|
|
99
|
+
- Apache-2.0
|
|
100
|
+
metadata:
|
|
101
|
+
homepage_uri: https://kiosk.tech
|
|
102
|
+
source_code_uri: https://github.com/kiosk-hq/kiosk
|
|
103
|
+
changelog_uri: https://github.com/kiosk-hq/kiosk/blob/main/kiosk-pay-stripe/CHANGELOG.md
|
|
104
|
+
bug_tracker_uri: https://github.com/kiosk-hq/kiosk/issues
|
|
105
|
+
rubygems_mfa_required: 'true'
|
|
106
|
+
rdoc_options: []
|
|
107
|
+
require_paths:
|
|
108
|
+
- lib
|
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
110
|
+
requirements:
|
|
111
|
+
- - ">="
|
|
112
|
+
- !ruby/object:Gem::Version
|
|
113
|
+
version: '4.0'
|
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
115
|
+
requirements:
|
|
116
|
+
- - ">="
|
|
117
|
+
- !ruby/object:Gem::Version
|
|
118
|
+
version: '0'
|
|
119
|
+
requirements: []
|
|
120
|
+
rubygems_version: 4.0.3
|
|
121
|
+
specification_version: 4
|
|
122
|
+
summary: Stripe PSP adapter for the Kiosk framework
|
|
123
|
+
test_files: []
|