Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 390
- Log:
Initial import of Canvass, a donations-based poll application.
- Author:
- rool
- Date:
- Mon Mar 21 14:58:04 +0000 2011
- Size:
- 7221 Bytes
1 | ######################################################################## |
2 | # File:: payment_gateway_onsite_controller.rb |
3 | # (C):: Hipposoft 2010, 2011 |
4 | # |
5 | # Purpose:: Handle order payment via on-site payment using external |
6 | # (but invisible to the user) web services for actual card |
7 | # verification and payments or refunds. Uses ActiveMerchant |
8 | # to abstract away from the actual payment method in use, but |
9 | # currently tested for PayPal Payflow Pro (AKA Website |
10 | # Payments Pro in the UK, but not in the US, where WPP is an |
11 | # entirely different thing). |
12 | # |
13 | # The code has been tested with PayPal direct payments, though |
14 | # provided the gateway supports 'authorize' and 'capture' for |
15 | # purchase, 'void' for cancellation and 'credit' for refunds it |
16 | # ought to work with this controller without modification. |
17 | # |
18 | # The controller is almost RESTful: |
19 | # |
20 | # GET new: Enter card details, submit form for 'create'. |
21 | # |
22 | # PUT create: Check card details and if OK render 'edit'. We render |
23 | # rather than redirect as additional temporary data from |
24 | # the form submission is needed in the new. 'Edit' shows |
25 | # the order confirmation page (very similar to that used |
26 | # for the offsite payment process). |
27 | # |
28 | # POST update: The edit form above POSTs here to make the |
29 | # final payment. |
30 | # |
31 | # MONEY IS PAID HERE and here only. |
32 | # |
33 | # GET delete: Cancel process at any stage. |
34 | # ---------------------------------------------------------------------- |
35 | # 11-Mar-2010 (ADH): Created. |
36 | # 30-Jan-2011 (ADH): Imported from Artisan. |
37 | ######################################################################## |
38 | |
39 | class PaymentGatewayOnsiteController < PaymentGatewayController |
40 | |
41 | # NB: Security and other filters - see the superclass. |
42 | |
43 | def delete; super; end |
44 | |
45 | # The main heading is skipped for action "create" as the desire to avoid |
46 | # storing card details anywhere permanent leads to an odd non-RESTful |
47 | # flow within the application. Since the form for "new" may be re-rendered |
48 | # if "create" encounters an error (e.g. validations), then if we skip the |
49 | # heading on the one action, we must skip it in the other too and deal |
50 | # with the repercussions in the view. |
51 | # |
52 | # new -> enter card details and submit to 'create' |
53 | # create -> check card details and if OK render 'edit' view so that |
54 | # the form submission state data from 'new' can be carried |
55 | # into the 'edit' form too; except 'edit' now renders from |
56 | # the 'create' action, so would get the wrong main heading |
57 | # by default. Since we only ever render 'edit' from 'create' |
58 | # upon success, there will be a flash message which will do |
59 | # instead. |
60 | # update -> Accept the 'edit' form (rendered from 'create', see above) |
61 | # and (try to) pay for and confirm the order. |
62 | # delete -> Delete the temporary donation data via the superclass. |
63 | # |
64 | def skip_main_heading? |
65 | action_name == 'new' || action_name == 'create' |
66 | end |
67 | |
68 | # Start a new payment session. |
69 | # |
70 | def new |
71 | |
72 | if ( PaymentGateway.instance.gateway_is_express_only() ) |
73 | raise "On-site checkout is impossible with an Express-only gateway" |
74 | end |
75 | |
76 | # Use the current user's non-unique (i.e. theoretically "normal") user name |
77 | # (rather than the Hub augmented unique name) as the card name initially. |
78 | # The user can edit this if they want. |
79 | |
80 | @item = PaymentCard.new |
81 | @item.card_name = hubssolib_get_user_name |
82 | |
83 | end |
84 | |
85 | # Take the payment details form and turn this into a PaymentCard instance. |
86 | # Handle errors with this, or if OK, reserve the order amount via the card |
87 | # and the payment gateway. Again, handle errors, or if OK, render the 'edit' |
88 | # view so the user can confirm their donation. |
89 | # |
90 | def create |
91 | @item = PaymentCard.new( params[ :payment_card ] ) |
92 | if ( @item.valid? ) |
93 | |
94 | begin |
95 | |
96 | gateway_response = gateway.authorize( |
97 | @donation.amount_for_gateway(), |
98 | @item.card, |
99 | :currency => @donation.currency.code |
100 | ) |
101 | |
102 | raise gateway_response.message unless ( gateway_response.success? ) |
103 | @donation.authorisation_tokens = gateway_response.authorization |
104 | |
105 | # Save the updated donation object and set things up so that the 'edit' |
106 | # view can be rendered - this matches up with the offsite payment |
107 | # gateway behaviour and presents the user with their final chance to |
108 | # cancel or confirm the order. The result will come in as a call to the |
109 | # 'update' action. |
110 | |
111 | @donation.save! |
112 | |
113 | @notes = '' |
114 | @address = { |
115 | 'name' => @item.card_name, |
116 | 'address1' => @item.address_1, |
117 | 'address2' => @item.address_2, |
118 | 'address3' => @item.address_3, |
119 | 'city' => @item.city, |
120 | 'state' => @item.state, |
121 | 'zip' => @item.postcode, |
122 | 'country' => @item.country |
123 | } |
124 | |
125 | # Leave the 'render' call out of the block. It shouldn't fail during |
126 | # production runs unless there's a bug in the code. If inside the |
127 | # exception handler, this results in a double render error when the |
128 | # redirection call below is made, obscuring the real problem. |
129 | |
130 | rescue => error |
131 | appctrl_report_error( error ) |
132 | redirect_to( root_path() ) |
133 | return |
134 | |
135 | end |
136 | |
137 | # Note early exit cases above - we only get here if everything worked. |
138 | |
139 | render( { :action => :edit } ) |
140 | |
141 | else # @item not valid -> re-render 'new' form to show the user the errors |
142 | render( { :action => :new } ) |
143 | |
144 | end |
145 | end |
146 | |
147 | # The user wants to confirm their order. |
148 | # |
149 | def update |
150 | begin |
151 | Donation.transaction do |
152 | |
153 | @donation.notes = params[ :notes ] || '' |
154 | @donation.invoice_number = InvoiceNumber.next! |
155 | |
156 | transaction_token_record = {} |
157 | transaction_token_record[ :onsite ] = {} |
158 | |
159 | gateway_response = gateway.capture( |
160 | @donation.amount_for_gateway(), |
161 | @donation.authorisation_tokens, |
162 | |
163 | :currency => @donation.currency.code, |
164 | :ip => request.remote_ip |
165 | ) |
166 | |
167 | raise gateway_response.message unless ( gateway_response.success? ) |
168 | |
169 | @donation.authorisation_tokens = {} |
170 | @donation.authorisation_tokens[ :onsite ] = gateway_response.authorization |
171 | @donation.paid! |
172 | @donation.save! |
173 | end |
174 | |
175 | # Success! |
176 | # |
177 | # See the Donation model for comments on why this is done here - this line |
178 | # of code causes all related notification e-mail messages to be sent. |
179 | |
180 | @donation.send_new_item_notifications() |
181 | |
182 | appctrl_set_flash( :notice ) |
183 | redirect_to( user_donation_path( :id => @donation.id, :user_id => @donation.user_id ) ) |
184 | |
185 | rescue => error |
186 | |
187 | # Report errors and hope the user can do something about it if need be! |
188 | |
189 | appctrl_report_error( error ) |
190 | redirect_to( root_path() ) |
191 | end |
192 | end |
193 | end |