solidusio / solidus_bolt Goto Github PK
View Code? Open in Web Editor NEWLicense: BSD 3-Clause "New" or "Revised" License
License: BSD 3-Clause "New" or "Revised" License
Bolt is different from a regular payment method in the sense that it also integrates with the user accounts and links them with the user's bolt account.
Since, bolt is a little different from the regular payment method, so we have to store it's configuration separately, so that we can access it outside of payment scenarios.
When Solidus makes changes to an order’s tracking status(updating the tracking_number), Bolt will need to be notified to pass this information to publishers as needed. Solidus will call Bolt’s APIs with updates.
"Refunded" is not a transaction status but a separate transaction that shows the completed
refund. This should not matter, Bolt will still be able to call a Solidus API to notify that the transaction
is refunded, it should just be another status mapping on the Solidus module side.
this is the Refund's payload
{
"type": "credit",
"object": "transaction",
"data": {
"id": "TAm5vRC1zYUsq",
"type": "cc_credit",
"processor": "vantiv",
"date": 1650658902485,
"reference": "PQK6-2FBJ-8PND",
"status": "completed",
"amount": {
"amount": 1000,
"currency": "USD",
"currency_symbol": "$"
},
"order": {
"cart": {
"order_reference": "R387090343",
"display_id": "R387090343",
"total_amount": {
"amount": 1000,
"currency": "USD",
"currency_symbol": "$"
},
"items": [
{
"reference": "",
"name": "Solidus T-Shirt",
"total_amount": {
"amount": 0,
"currency": "USD",
"currency_symbol": "$"
},
"unit_price": {
"amount": 1999,
"currency": "USD",
"currency_symbol": "$"
},
"tax_amount": {
"amount": 0,
"currency": "USD",
"currency_symbol": "$"
},
"quantity": 1,
"sku": "SOL-00003",
"properties": [],
"taxable": true,
"type": "unknown"
}
],
"subtotal_amount": {
"amount": 0,
"currency": "USD",
"currency_symbol": "$"
},
"discount_amount": {
"amount": 0,
"currency": "USD",
"currency_symbol": "$"
}
}
},
"captures": null,
"refund_transactions": [],
"source_transaction": {
"id": "TA9W3jwZtX5eA",
"reference": "Q4GB-3PK7-YXMR",
"amount": {
"amount": 1000,
"currency": "USD",
"currency_symbol": "$"
}
},
"risk_signals": {
"ip_address": "24.5.198.185",
"time_on_site": "00:00:00",
"http_headers": {
"accept": "application/json",
"accept_encoding": "gzip, deflate, br",
"accept_language": "en-US,en;q=0.9",
"connection": "",
"host": "api-sandbox.bolt.com",
"referer": "https://merchant-sandbox.bolt.com/",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"
}
},
"capture_type": "manual",
"refund_type": "single_refund",
"total_refund_amount": {
"amount": 1000,
"currency": "USD",
"currency_symbol": "$"
},
"requested_refund_amount": {
"amount": 1000,
"currency": "USD",
"currency_symbol": "$"
}
}
}
Considering that the Bolt Checkout should handle the checkout from the beginning retrieving the account information etc.., I think that SolidusBolt::BoltCheckout would be confusing. This is more a BoltPayment
or something like that. What do you think to rename it now before it will be too late?
Bolt integration will require to use of webhooks.
There are some works to do in order to let them work properly.
This page http://localhost:3000/admin/orders/R185775828/payments/5 is failing with missing partial for bolt payment.
Ref https://github.com/nebulab/solidus_bolt/blob/master/app/models/solidus_bolt/gateway.rb#L7-L9
This method will receive the amount, payment_source, and gateway_options and should create Spree::Payment.
The Spree::Payment should have the Transaction.reference, retrieved by Authorize Service stored on the response_code.
It would be nice to store even the credit_card_id on the SolidusBolt::PaymentSource, so by providing a method SolidusBolt::PaymentSource#reusable? which returns true
, the payment source will be shown to the user for further payment.
Reading the addresses of the Bolt account by the service created from #76, add the addresses to the User's Address Books using save_in_address_book method.
During the checkout the console has reported an error:
ActionView::Template::Error (Missing partial spree/api/payments/source_views/_bolt with {:locale=>[:en], :formats=>[:json], :variants=>[], :handlers=>[:jbuilder]}. Searched in:
* "/Users/danielepalombo/Code/solidus_bolt/sandbox/app/views"
* "/Users/danielepalombo/.asdf/installs/ruby/2.7.6/lib/ruby/gems/2.7.0/gems/solidus_social-1.4.0/app/views"
* "/Users/danielepalombo/Code/solidus_bolt/app/views"
This should be related with the request:
curl 'http://localhost:3000/api/checkouts/R185775828' \ -X 'PATCH' \ -H 'Accept: */*' \ -H 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' \ -H 'Cache-Control: no-cache' \ -H 'Connection: keep-alive' \ -H 'Content-Type: application/json' \ -H 'Origin: http://localhost:3000' \ -H 'Pragma: no-cache' \ -H 'Referer: http://localhost:3000/checkout/payment' \ -H 'Sec-Fetch-Dest: empty' \ -H 'Sec-Fetch-Mode: cors' \ -H 'Sec-Fetch-Site: same-origin' \ -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36' \ -H 'X-Spree-Order-Token: pd9_p9qXw30Td77qeAe6ZQ' \ -H 'sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="101", "Google Chrome";v="101"' \ -H 'sec-ch-ua-mobile: ?0' \ -H 'sec-ch-ua-platform: "macOS"' \ --data-raw '{"order":{"payments_attributes":[{"payment_method_id":"4","source_attributes":{"card_token":"0db3b56d69830f6d6d3a4af0c4a6fc07064b691fab02d561572bbcabddb920a2","card_last4":"1111","card_bin":"411111","card_expiration":"2022-11","create_bolt_account":true}}]}}' \ --compressed
When a Transaction is in completed state the Payment should be completed in solidus.
In order to prevent capturing the payment again, we have to fill the Spree::Payment#response_code attribute to prevent capturing the payment multiple time.
This is the event payload of the event:
{
"type": "capture",
"object": "transaction",
"data": {
"id": "TAimWZydQKNUQ",
"type": "cc_payment",
"processor": "vantiv",
"date": 1650649010783,
"reference": "V2YW-NYNR-2MYM",
"status": "completed",
"from_user": {
"id": "CA7w2RKPi9PKG",
"first_name": "Daniele",
"last_name": "Palombo",
"phones": [
{
"number": "+1 333222334232",
"country_code": "1"
}
],
"emails": [
{
"address": "[email protected]"
}
]
},
"from_credit_card": {
"id": "CAan4YBg23RYj",
"last4": "1111",
"bin": "411111",
"expiration": 1667260800000,
"network": "Visa",
"display_network": "Visa",
"token_type": "bolt",
"status": "active"
},
"amount": {
"amount": 1200,
"currency": "USD",
"currency_symbol": "$"
},
"authorization": {
"auth": "83988858285003417",
"reason": "none",
"status": "succeeded"
},
"captures": [
{
"id": "CAh9W58M3z8Pj",
"status": "succeeded",
"amount": {
"amount": 1200,
"currency": "USD",
"currency_symbol": "$"
}
}
],
"refund_transactions": [],
"risk_signals": {
"ip_address": "79.35.218.113",
"time_on_site": "00:00:00",
"http_headers": {
"accept": "application/json",
"accept_encoding": "gzip, deflate, br",
"accept_language": "en-GB,en-US;q=0.9,en;q=0.8",
"connection": "",
"host": "api-sandbox.bolt.com",
"referer": "https://merchant-sandbox.bolt.com/",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"
}
},
"capture_type": "manual"
}
}
use data.reference
to retrieve the payment and advance it to the complete state
Implement the credit method.
Create the above object with the related preferences and methods.
ref API Docs
Bolt is different from a regular payment method in the sense that it also integrates with the user accounts and links them with the user's bolt account.
Since, bolt is a little different from the regular payment method, so we have to store it's configuration separately, so that we can access it outside of payment scenarios.
api_key
, signing_secret
, publishable_key
and merchant_id
In order to create the Bolt transaction and store payment on solidus we should get the right information from the js API, and call the Checkout update API providing the right payment and source structure.
Then we have to implement the SolidusGateway::Gateway#authorize method.
This method will receive the amount, payment_source, and gateway_options and should create Spree::Payment.
The PaymentSource should have the credit card info tokenized on the payment source, so that calling Authorize Service it will give back the transaction reference to store on the response_code.
It would be nice to store even the credit_card_id on the SolidusBolt::PaymentSource, so by providing a method SolidusBolt::PaymentSource#reusable? which returns true
, the payment source will be shown to the user for further payment.
This is dependent on #13
The merchant onboarding flow should be built into the extension.
These are the broad steps of the merchant onboarding process.
As a first step in the adoption journey, merchants will create a merchant account on Bolt.
This will make use of Bolt-hosted UI and the steps will include information verification for their
Solidus Store information as well as acceptance of Bolt’s terms & policies.
Solidus should provide a link to the admin user that will take them to the merchant account registration page to make it easier for them to adopt bolt.
Payment Processor Addition to Bolt
Once a Bolt merchant account has been created, the merchant will need to connect to a Bolt supported payment processor in order to start receiving orders.
Bolt API Key, Publishable Key and Signing Secret Generation
Upon adding the payment processor, Bolt will generate keys required for setting up Bolt on
Solidus, and redirect the merchant back to Solidus.
Solidus will need to ensure that the API Key, Publishable Key and Signing Secret are auto-filled on the merchant’s Solidus Store.
This endpoint should receive the authorizationCode
and scope
retrieved by embedded component, and using the oauth/token service It should retrieve the id_token and access_token
Using a Specific Bolt OmniAuth Strategy the access_token will be stored in the user session after the SSO login process.
In the following months, we should deal with a lot of API calls for Bolt. These will be Merchant, Account, and Transaction APIs. It is worth it to add a "Gateway Object" to handle the requests and responses rather than using the HTTPARTY gem anywhere that would make the request payload creation and authentication hard to use and test.
We have already done it for darkstore with faraday in the past and the process was smooth and we have got a lot of benefits during the development, but we can consider using httparty this time.
solidus_bolt uses the bolt_access_token for a lot of its API calls. This token expires after 15 minutes and when it does, these calls raise a Server Error which block the app.
Since it can very easily happen that a user would be logged in for over 15 minutes, to avoid breaking the user experience we should log out the user whenever the token expires
the computed_hmac doesn't match the header_hmac and we are unsure why. Need to check with Bolt and ask for guidance
At the moment, BoltConfiguration fetch uses a first_or_create method that has all the configuration field empty.
It would be nice adding the BoltConfiguration to the seed and fill the keys with the BOLT_*
variable present on the local environament.
Solidus will need to build an endpoint for Bolt to call and determine whether there is an existing
merchant storefront account associated with a particular email. This endpoint should accept POST
requests that are signed with Bolt’s Signing Secret and contain the shopper’s email address. When
no account exists, the endpoint should return a 404 error. The endpoint can be whatever Solidus
wants, so long as this is specified to Bolt.
Bolt Authorize API accepts shipping address information.
Add shipping_address field to the Authorize payload
Add register link to Bolt configuration page https://merchant.bolt.com/register
See PDF pages 16-20
Goal: A guest shopper can:
● Opt-into creating a Bolt account
● Have their CC saved to their Bolt account
Additionally, order statuses will be synced whether the retailer interacts with Solidus or with Bolt
Steps
Reading the payment_methods of the Bolt account by the service created from #76, add the PaymentSource to the user.
This requires a new relation between SolidusBolt::PaymentSource(belongs_to optional: true) and Spree::User(has_many).
As specified on #86, we should store the credit_card_id in order to re-use the source.
Rel: #7
We should provide an endpoint that will return a JSON document with all the users and the related info:
email
first_name
last_name
phone # from shipping address
addresses: [{
street_address # address_1 + address_2
locality # city
region # state iso code
postal code # zipcode
country # Country iso code
}]
At the moment the README is not very clear and for new developer would be hard to integrate solidus_bolt in their current application.
We should add some instruction on how and which env variable should be setup and where to go in order to setup the BoltConfiguration.
When Transation gets in Cancelled state, we have to void the Solidus Payment.
This is the Event payload we'll receive from Bolt:
{
"type": "void",
"object": "transaction",
"data": {
"id": "TA5dS8mv4KBHp",
"type": "cc_payment",
"processor": "vantiv",
"date": 1650650380858,
"reference": "XMM7-KLJZ-K9B3",
"status": "cancelled",
"from_user": {
"id": "CA7w2RKPi9PKG",
"first_name": "Daniele",
"last_name": "Palombo",
"phones": [
{
"number": "+1 333222334232",
"country_code": "1"
}
],
"emails": [
{
"address": "[email protected]"
}
]
},
"from_credit_card": {
"id": "CAan4YBg23RYj",
"last4": "1111",
"bin": "411111",
"expiration": 1667260800000,
"network": "Visa",
"display_network": "Visa",
"token_type": "bolt",
"status": "active"
},
"amount": {
"amount": 2000,
"currency": "USD",
"currency_symbol": "$"
},
"authorization": {
"auth": "83988858395380762",
"reason": "none",
"status": "succeeded"
},
"captures": null,
"refund_transactions": [],
"risk_signals": {
"ip_address": "79.35.218.113",
"time_on_site": "00:00:00",
"http_headers": {
"accept": "application/json",
"accept_encoding": "gzip, deflate, br",
"accept_language": "en-GB,en-US;q=0.9,en;q=0.8",
"connection": "",
"host": "api-sandbox.bolt.com",
"referer": "https://merchant-sandbox.bolt.com/",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"
}
},
"capture_type": "manual"
}
}
use data.reference
to retrieve the payment and advance it to the void state
Implement the purchase method.
This is called when the payment method has auto-capture set to true, it will be the same as authorize with auto_capture = true.
Consider to re-use the AuthorizeService and specify auto_capture to true
When the merchant will have completed the Bolt Onboarding process started from solidus, Bolt will redirect the user to a solidus page that should fill all the missing keys in order to let the payment method work.
Following the onboarding UML diagram the Merchant will lend on a solidus page and it should:
/v1/merchants/<merchant_id>/config
endpoint to get API key, Signing Secret & Publishable Key./v1/merchants/<merchant_id>/config
endpoint to enable the paymentWe need a new service for oauth/token API that should receive the authorizationCode
and Scope
and give back access token payload.
{
"access_token": "DdVJx7rNH1HUp5feAls31Gwe7QLsZTL96_BW3O_OPXI.Jwewe38Pt0uMs2zD-mu89BNu_o2AqXqBoq1C38kQkfHFc",
"expires_in": 3599,
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImU4YTcyNzNkLTQwZDQtNGJlMS1hODI2LWY5MTEzZThiZjBhYiIsInR5cCI6IkpXVCJ9.eyJhdF9oYXNoIjoiNTRBV2tjQ2pfLVBXQjJKWUtMU29FUSIsImF1ZCI6WyJDQl9GdGZHZFVQMmUuUnE0cUIxUWFqWUxuLmMyM2RkYTEwOWZlNDYwYzY5NzlmZjU3OTExYjRhMjU3YjFiNTI1OweZlNjNhY2IyNTE5NjUxMTA0Y2NiMzdkZDUiXSwiYXV0aF90aW1lIjoxNjUxNjA3NzQzLCJjdXN0b21fZmllbGRzIjpbXSwiZW1haWsdfiOiJkYW5pZWxlcGFsb21ibytib2x0QG5lYnVsYWIuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTY1MTYxMTM0MywiZmlyc3RfbmFtZSI6IkFsZXgiLCJpYXQiOjE2NTE2MDc3NTYsImlzcyI6Imh0dHBzOi8vYXBpLXNhbmRib3guYm9sdC5jb20iLCJqdGkiOiJiYmJkNTBlYy00MjE3LTRmMjgtYjZjNi1jYzUzYTU2YTA5YWEiLCJsYXN0X25hbWUiOiJIb25ub2xkIiwicmF0IjoxNjUxNjA3NzQzLCJzdWIiOiJjZDVkMzA1MS1jODFlLTQ0ZjMtYWViZC05NjkzOTMxM2RmZDQifQ.O5rtD25hi9dOf1b8b7no5_1M6TzPbpqVIx0JWf8JqhZmhZX_51XQ2pgp-tyN5FaQdNllF7qRhwWRgmhWTt2aY9trYLwVvdTdGrVH9GJTdKzAr1BXMczqCFZ6vllvw4IngFd0s63QEX-yb5B-oBBsQAuaT0CitdC36u7rgC4DC7X3Pyn5weQu2hTYU_2_FmK7lcvW-ATZ9D5Y5ApFG7Csn8p-jeRDmOWxsYzFnoxk_2sU69FSVkcRrNc0LtPQU4o6hBLWloEI2E-FHqN6a70fosIWYaZmG4oWtYA839YncNV4nRT_7YfGCRZeaY-fhmydFk80N8K_bIjU8G5reMLVX60yEpV3yt9RFFQeECeIyTJtkMe451UCtXuzDk0BhcVso4B3ldpNzDGS6kuylWs6HCLRk-Aec3JefefbdYOMYps7LUuCrIM9MUNUGxRTRhe095bgeM0B4EHv8mIYHw-bQHmCbPzZeQOi2rsdm4AbHs8EWWsy2ie0HIqlcHVRdPcs9AVs3pU3qROCGMfJKEX8sNkyjlO9bTCO-eiQlWekySceZrFjrlABMKzMfRsljx6Et6dfvFAoKtnn21d4pSxL0Ng1MLvgKpgOtUCO70D51Ua3rC0yS_Wan6NdFz_EgKib4CsBXVQ2Bm0X7LhYblFPKMRK8sr9CJMrMEvn0RK3tMM",
"scope": "openid bolt.account.manage",
"token_type": "bearer"
}
We want to remove human process and automatically update the Demo to display the latest version of the SolidusBolt gem
Check if the logged-in user has bolt provider as AuthenticationMethod, if so don't show the create bolt account
checkbox during the payment checkout step
Implement the ability to void an authorized Payment
It also requires implementing the can_void? method
Add a service that retrieves the Account information of a certain user authorized with the access token using the BOLT GetAccountDetails API
Following this documentation we have to provide the ability to:
There is a new strategy to load the bolt script and login/register button. We have to integrate it following the instruction provided by:
After the sign in the user will be redirected on /users/oauth/bolt
endpoint and will have the parameters code, state, scope.
In order to let it work with our SSO we have to change the request_phase on omniauth-bolt and the code that redirect the user to that page
It should be enough to replace the authorization_code
with just the code
work.
In order to verify the authenticity of the account, is required to check also the ID token thought the jwks tokens.
The new onboarding merchant API will return a payload like this:
{
"is_enabled": true,
"config_mode": "<the checkout mode>",
"merchant_id": "<the merchant ID>",
"bolt_credentials": {
"api_key" : "<the API key>",
"signing_secret" : "<the signing secret>",
"publishable_key" : "<the publishable key>",
}
}
We should store this information in the database. In order to do that we should:
All API services are returning the HTTParty response, this means that the caller should deal with HTTP status code and parse the result. This is something that we want to avoid, so it would be nice adding a method to handle the response and give to the caller just the result of the operation.
We'd like to build towards the ideal and most frictionless experience, to achieve that goal it would be nice to allow solidus to set webhook URLs via API.
My proposal is to add a link on the BoltConfiguration page that calls the Bolt API endpoint and create a webhook to the provided route url.
During the payment step of the checkout, solidus has to propose the existing payment.
We can achieve that goal using the reusable
feature provided by solidus.
Solidus shows to the user all the Spree::PaymentSource that return reusable? == true, so we have to override that method on SolidusBolt::PaymentSource to let it to return true anyway.
Bolt allows to Authorize Card by token or credit_card_id, at the moment the SolidusBolt::PaymentSource#card_id is not set. We should set it after the transaction is authorized taking the id of the card from the response body of the Authorize API using the path [:transaction][:from_credit_cart][:id]
.
Then we have to change the code to authorize the transaction in order to use the card_id whenever is present.
Add a specific template at app/views/spree/checkout/existing_payment/_bolt.html.erb
to show the bolt payment sources.
Implement the ability to capture an authorized payment.
It also requires implementing the can_capture? method.
ref https://github.com/nebulab/solidus_bolt/blob/master/app/models/solidus_bolt/gateway.rb#L24-L26
Demo application doesn't have any product/users etc. In order to test it we should provide at least the sample data.
When a new address is added in the user AddressBook it should be added to the bolt account through the API https://help.bolt.com/api-bolt/#tag/Account/operation/AddAddress
We can fire a specific event at this point to decouple the logic and add the address to bolt account.
The first part of Automated Onboarding for Embedded Checkout
is to confirm the merchant details on a Bolt hosted UI
.
We have to provide a way for the new merchants to create a Bolt account from the solidus admin interface.
Get onboarding URL
, create a new Payment method with the received merchant ID and redirect the user to the Bolt Onboarding Form URL
provided by the APIA declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.