-
Notifications
You must be signed in to change notification settings - Fork 503
Add Lightspeed Reatail provider #1373
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dansleboby
wants to merge
3
commits into
SocialiteProviders:master
Choose a base branch
from
dansleboby:master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| <?php | ||
|
|
||
| namespace SocialiteProviders\LightspeedRetail; | ||
|
|
||
| use SocialiteProviders\Manager\SocialiteWasCalled; | ||
|
|
||
| class LightspeedRetailExtendSocialite | ||
| { | ||
| /** | ||
| * Register the provider. | ||
| * | ||
| * @param \SocialiteProviders\Manager\SocialiteWasCalled $socialiteWasCalled | ||
| * @return void | ||
| */ | ||
| public function handle(SocialiteWasCalled $socialiteWasCalled): void | ||
| { | ||
| $socialiteWasCalled->extendSocialite('lightspeedretail', Provider::class); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,205 @@ | ||
| <?php | ||
|
|
||
| namespace SocialiteProviders\LightspeedRetail; | ||
|
|
||
| use GuzzleHttp\RequestOptions; | ||
| use Laravel\Socialite\Two\InvalidStateException; | ||
| use SocialiteProviders\Manager\Contracts\OAuth2\ProviderInterface; | ||
| use SocialiteProviders\Manager\OAuth2\AbstractProvider; | ||
| use SocialiteProviders\Manager\OAuth2\User; | ||
|
|
||
| class Provider extends AbstractProvider implements ProviderInterface | ||
| { | ||
| const IDENTIFIER = 'LIGHTSPEEDRETAIL'; | ||
|
|
||
| protected $scopes = []; | ||
|
|
||
| /** | ||
| * The domain prefix for the current OAuth flow. | ||
| * | ||
| * @var string|null | ||
| */ | ||
| protected $domainPrefix = null; | ||
|
|
||
| protected function getAuthUrl($state) | ||
| { | ||
| return $this->buildAuthUrlFromBase('https://secure.retail.lightspeed.app/connect', $state); | ||
| } | ||
|
|
||
| /** | ||
| * Get the token URL for the provider. | ||
| * For the initial request, we must use the domain_prefix from the callback. | ||
| * | ||
| * @return string | ||
| */ | ||
| protected function getTokenUrl() | ||
| { | ||
| // We must get the domain_prefix from the callback for the initial token request | ||
| $domainPrefix = request()->input('domain_prefix', $this->domainPrefix); | ||
|
|
||
| if (empty($domainPrefix)) { | ||
| throw new \InvalidArgumentException( | ||
| 'Domain prefix is required to get the token URL. Make sure it is passed in the callback.' | ||
| ); | ||
| } | ||
|
|
||
| return "https://{$domainPrefix}.retail.lightspeed.app/api/1.0/token"; | ||
| } | ||
|
|
||
| /** | ||
| * Get the base API URL. | ||
| * | ||
| * @return string | ||
| */ | ||
| protected function getBaseUrl() | ||
| { | ||
| return sprintf('https://%s.retail.lightspeed.app/api', $this->getDomainPrefix()); | ||
| } | ||
|
|
||
| /** | ||
| * Get the domain prefix. | ||
| * | ||
| * @return string | ||
| */ | ||
| protected function getDomainPrefix() | ||
| { | ||
| if ($this->domainPrefix) { | ||
| return $this->domainPrefix; | ||
| } | ||
|
|
||
| // For subsequent calls after token retrieval | ||
| $configPrefix = $this->getConfig('domain_prefix'); | ||
| if (!empty($configPrefix)) { | ||
| return $configPrefix; | ||
| } | ||
|
|
||
| throw new \InvalidArgumentException( | ||
| 'No domain_prefix found. It should be received from the token response ' . | ||
| 'or configured as a default in the config.' | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritdoc} | ||
| */ | ||
| public function user() | ||
| { | ||
| if ($this->hasInvalidState()) { | ||
| throw new InvalidStateException(); | ||
| } | ||
|
|
||
| $response = $this->getAccessTokenResponse($this->getCode()); | ||
|
|
||
| // Store the domain_prefix from the token response | ||
| if (isset($response['domain_prefix'])) { | ||
| $this->domainPrefix = $response['domain_prefix']; | ||
| } | ||
|
|
||
| $userData = $this->getUserByToken( | ||
| $token = $this->parseAccessToken($response) | ||
| ); | ||
|
|
||
| // Add domain prefix to the raw user data | ||
| $userData['domain_prefix'] = $this->domainPrefix; | ||
|
|
||
| $user = $this->mapUserToObject($userData); | ||
|
|
||
| return $user->setToken($token) | ||
| ->setRefreshToken($this->parseRefreshToken($response)) | ||
| ->setExpiresIn($this->parseExpiresIn($response)); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritdoc} | ||
| */ | ||
| protected function getUserByToken($token) | ||
| { | ||
| $response = $this->getHttpClient()->get($this->getBaseUrl().'/2.0/user', [ | ||
| RequestOptions::HEADERS => [ | ||
| 'Authorization' => 'Bearer '.$token, | ||
| ], | ||
| ]); | ||
|
|
||
| return json_decode((string) $response->getBody(), true)['data'] ?? []; | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritdoc} | ||
| */ | ||
| protected function mapUserToObject(array $user) | ||
| { | ||
| return (new User)->setRaw($user)->map([ | ||
| 'id' => $user['id'], | ||
| 'nickname' => $user['display_name'], | ||
| 'name' => $user['username'], | ||
| 'email' => $user['email'] | ||
| ]); | ||
| } | ||
|
|
||
| /** | ||
| * Set the domain prefix for API calls. | ||
| * | ||
| * @param string $domainPrefix | ||
| * @return $this | ||
| */ | ||
| public function setDomainPrefix($domainPrefix) | ||
| { | ||
| $this->domainPrefix = $domainPrefix; | ||
|
|
||
| return $this; | ||
| } | ||
|
|
||
| /** | ||
| * Get access token response from provider | ||
| * | ||
| * @param string $code | ||
| * @return array | ||
| */ | ||
| public function getAccessTokenResponse($code) | ||
| { | ||
| $response = parent::getAccessTokenResponse($code); | ||
|
|
||
| // Store the domain_prefix from the token response | ||
| if (isset($response['domain_prefix'])) { | ||
| $this->domainPrefix = $response['domain_prefix']; | ||
| } | ||
|
|
||
| return $response; | ||
| } | ||
|
|
||
| /** | ||
| * Refresh a token. | ||
| * | ||
| * @param string $refreshToken | ||
| * @return array | ||
| */ | ||
| public function refreshToken($refreshToken) | ||
| { | ||
| if (!$this->domainPrefix) { | ||
| throw new \InvalidArgumentException( | ||
| 'Domain prefix must be set before refreshing tokens. Use setDomainPrefix() method.' | ||
| ); | ||
| } | ||
|
|
||
| $refreshTokenUrl = "https://{$this->domainPrefix}.retail.lightspeed.app/api/1.0/token"; | ||
|
|
||
| $response = $this->getHttpClient()->post($refreshTokenUrl, [ | ||
| 'headers' => ['Accept' => 'application/json'], | ||
| 'form_params' => [ | ||
| 'grant_type' => 'refresh_token', | ||
| 'refresh_token' => $refreshToken, | ||
| 'client_id' => $this->clientId, | ||
| 'client_secret' => $this->clientSecret, | ||
| ], | ||
| ]); | ||
|
|
||
| $refreshedToken = json_decode((string) $response->getBody(), true); | ||
|
|
||
| // Update domain prefix in case it changed | ||
| if (isset($refreshedToken['domain_prefix'])) { | ||
| $this->domainPrefix = $refreshedToken['domain_prefix']; | ||
| } | ||
|
|
||
| return $refreshedToken; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| # Lightspeed Retail | ||
|
|
||
| ```bash | ||
| composer require socialiteproviders/lightspeedretail | ||
| ``` | ||
|
|
||
| ## Installation & Basic Usage | ||
|
|
||
| Please see the [Base Installation Guide](https://socialiteproviders.com/usage/), then follow the provider specific instructions below. | ||
|
|
||
| ### Add configuration to `config/services.php` | ||
|
|
||
| ```php | ||
| 'lightspeedretail' => [ | ||
| 'client_id' => env('LIGHTSPEEDRETAIL_CLIENT_ID'), | ||
| 'client_secret' => env('LIGHTSPEEDRETAIL_CLIENT_SECRET'), | ||
| 'redirect' => env('LIGHTSPEEDRETAIL_REDIRECT_URI') | ||
| ], | ||
| ``` | ||
|
|
||
| ### Add provider event listener | ||
|
|
||
| #### Laravel 11+ | ||
|
|
||
| In Laravel 11, the default `EventServiceProvider` provider was removed. Instead, add the listener using the `listen` method on the `Event` facade, in your `AppServiceProvider` `boot` method. | ||
|
|
||
| * Note: You do not need to add anything for the built-in socialite providers unless you override them with your own providers. | ||
|
|
||
| ```php | ||
| Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) { | ||
| $event->extendSocialite('lightspeedretail', \SocialiteProviders\LightspeedRetail\Provider::class); | ||
| }); | ||
| ``` | ||
| <details> | ||
| <summary> | ||
| Laravel 10 or below | ||
| </summary> | ||
| Configure the package's listener to listen for `SocialiteWasCalled` events. | ||
|
|
||
| Add the event to your `listen[]` array in `app/Providers/EventServiceProvider`. See the [Base Installation Guide](https://socialiteproviders.com/usage/) for detailed instructions. | ||
|
|
||
| ```php | ||
| protected $listen = [ | ||
| \SocialiteProviders\Manager\SocialiteWasCalled::class => [ | ||
| // ... other providers | ||
| \SocialiteProviders\LightspeedRetail\LightspeedRetailExtendSocialite::class.'@handle', | ||
| ], | ||
| ]; | ||
| ``` | ||
| </details> | ||
|
|
||
| ### Usage | ||
|
|
||
| You should now be able to use the provider like you would regularly use Socialite (assuming you have the facade installed): | ||
|
|
||
| ```php | ||
| return Socialite::driver('lightspeedretail')->redirect(); | ||
| ``` | ||
|
|
||
| ### Returned User fields | ||
|
|
||
| - ``id`` | ||
| - ``nickname`` | ||
| - ``name`` | ||
| - ``email`` | ||
|
|
||
| ### Domain Prefix Management | ||
|
|
||
| Lightspeed Retail uses a domain prefix (e.g., `example` in `example.retail.lightspeed.app`) that is unique to each retailer account. This prefix is returned in the OAuth response and must be stored and used for all future API calls. | ||
|
|
||
| ```php | ||
| // Get user with domain prefix | ||
| $user = Socialite::driver('lightspeedretail')->user(); | ||
| $domainPrefix = $user->user['domain_prefix']; | ||
|
|
||
| // Store domain prefix with tokens for future use | ||
| $authUser = User::updateOrCreate([ | ||
| 'email' => $user->email | ||
| ], [ | ||
| 'name' => $user->name, | ||
| 'lightspeed_token' => $user->token, | ||
| 'lightspeed_refresh_token' => $user->refreshToken, | ||
| 'lightspeed_domain_prefix' => $domainPrefix, | ||
| 'token_expires_at' => now()->addSeconds($user->expiresIn) | ||
| ]); | ||
|
|
||
| // When making API calls later, set the domain prefix first | ||
| $provider = Socialite::driver('lightspeedretail') | ||
| ->setDomainPrefix($authUser->lightspeed_domain_prefix); | ||
|
|
||
| // For refreshing tokens | ||
| $refreshedToken = $provider->refreshToken($authUser->lightspeed_refresh_token); | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| { | ||
| "name": "socialiteproviders/lightspeedretail", | ||
| "description": "LightspeedRetail OAuth2 Provider for Laravel Socialite", | ||
| "license": "MIT", | ||
| "keywords": [ | ||
| "lightspeedretail", | ||
| "laravel", | ||
| "oauth", | ||
| "oauth2", | ||
| "provider", | ||
| "socialite" | ||
| ], | ||
| "authors": [ | ||
| { | ||
| "name": "Gilbert Paquin", | ||
| "email": "gpaquin@agencecogix.com" | ||
| } | ||
| ], | ||
| "support": { | ||
| "issues": "https://github.com/socialiteproviders/providers/issues", | ||
| "source": "https://github.com/socialiteproviders/providers", | ||
| "docs": "https://socialiteproviders.com/lightspeedretail" | ||
| }, | ||
| "require": { | ||
| "php": "^8.0", | ||
| "ext-json": "*", | ||
| "socialiteproviders/manager": "^4.4" | ||
| }, | ||
| "autoload": { | ||
| "psr-4": { | ||
| "SocialiteProviders\\LightspeedRetail\\": "" | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it safe to get this from the url?
example.com/#results incurl 'https://example.com/#.retail.lightspeed.app/api/1.0/token'