Giter Site home page Giter Site logo

l4nos / laravel-cashier-stripe-connect Goto Github PK

View Code? Open in Web Editor NEW
60.0 60.0 26.0 152 KB

💲 Adds Stripe Connect functionality to Laravel's main billing package, Cashier. Simply works as a drop-in on top of Cashier, with no extra configuration.

Home Page: https://updev-1.gitbook.io/cashier-for-connect/

License: MIT License

PHP 100.00%

laravel-cashier-stripe-connect's People

Contributors

c-fitzmaurice avatar expdev07 avatar f-yousri avatar fwartner avatar gogl92 avatar hbakouane avatar jefhar avatar juliancc avatar l4nos avatar mariusspring avatar miagg avatar oleg-kolzhanov avatar striebwj avatar synergy6 avatar talelmishali avatar tsotnekekelia avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

laravel-cashier-stripe-connect's Issues

Logic Error: the connected customer does not work

public function stripeCustomerMapping()
    {
        return $this->belongsTo(ConnectCustomer::class, $this->primaryKey, $this->getLocalIDField())->where('model', '=', get_class($this));
    }

A customer can be dealing with multiple merchants. This is assuming that only one record for each customer is stored in the database. This simply means a customer can only deal with one merchant. As it is for connected accounts, the entire logic here breaks. When creating a customer, you recorded the merchant account id but then why here when you retrieve a customer you did not provide the merchant account id? If this is the case, there shouldn't be a pivot mapping table.

Payment Links for connected accounts

I'd like to use the Stripe payment links whats the best practice to get this plugin work with this flow?

Is it necessary to create the subscription after a successful payment manually or does it work with webhooks?

destination charges rounding precision should be 0

application_fee_amount is expected to be an integer however it's rounded with precision 2 in CanCharge trait in createDestinationCharge function.

// APPLY PLATFORM FEE COMMISSION - SET THIS AGAINST THE MODEL
        if (isset($this->commission_type) && isset($this->commission_rate)) {
            if ($this->commission_type === 'percentage') {
                $options['application_fee_amount'] = round($this->calculatePercentageFee($amount),2);
            } else {
                $options['application_fee_amount'] = round($this->commission_rate ,2);
            }
        }

        return PaymentIntent::create($options, $this->stripeAccountOptions());

for example, if the charge is 3 USD and the commission is 5% it results in error

Stripe\Exception\InvalidRequestException

Invalid integer: 15.75

Happy to write a pr if what I am suggesting is correct

Update doc link

Hello!

Loving the package, it has been an absolute lifesaver for me.

One small thing that I couldn't PR but thought might be worth pointing out, could you update the doc link on the left-hand side of the Github page to point your updated docs in the readme?
Screenshot 2022-12-15 at 16 40 22

This caught me out a few times when I accidentally opened the docs from the old repo and not found what I need.

All the best!

Add Unit tests

As a developer I'd like to be able to know if my changes are working and it doesn't affect other functionalities.

  • Add PHP unit
  • Add tests for current functionalities

Laravel10 Cashier 15.2

Hi all,

Does anyone happen to know if this will work with the above, or need a lot of modification due to the Stripe Api version?
Trying to avoid reinventing the wheel.
Enviroment PHP8.2, Fresh install of Laravel10 + Cashier v15.2

Thanks in advance.

Possibility to override Models

I'm currently using MongoDB in my Laravel application, to use this extension I need the possibility to override the Models being used that way I have more control and customization of the Models that interact with Stripe Connect.

Models inside the extension should be used by default but can be override by the cashierconnect.php.

Documentation changes

Hi @l4nos,

Firstly thanks heaps for keeping this package alive and improving upon it :)

I would like to contribute to the docs and was wondering if you could point me in the right direction to do so?

Specifically on the onboarding-accounts page change the handle on boarding method from:

`
private function handleBoardingRedirect(Store $Store): RedirectResponse
{
// Redirect to dashboard if onboarding is already completed.
if ($store->hasStripeAccountId() && $store->hasCompletedOnboarding()) {
return $store->redirectToAccountDashboard();
}

    // Delete account if already exists and create new express account with 
    // weekly payouts.
    $store->deleteAndCreateStripeAccount('express', [
        'settings' => [
            'payouts' => [ 
                'schedule' => [ 
                    'interval' => 'weekly', 
                    'weekly_anchor' => 'friday',
                ]
            ]
        ]
    ]);

    // Redirect to Stripe account onboarding, with return and refresh url, otherwise.
    return $store->redirectToAccountOnboarding(
        URL::to('/api/stripe/return?api_token=' . $store->api_token),
        URL::to('/api/stripe/refresh?api_token=' . $store->api_token)
    );
}

`

to:

`
private function handleBoardingRedirect(Store $Store): RedirectResponse
{
// Redirect to dashboard if onboarding is already completed.
if ($store->hasStripeAccount() && $store->hasCompletedOnboarding()) {
return $store->redirectToAccountDashboard();
}

    // Delete account if already exists and create new express account with 
    // weekly payouts.
    $store->deleteAndCreateStripeAccount('express', [
        'settings' => [
            'payouts' => [ 
                'schedule' => [ 
                    'interval' => 'weekly', 
                    'weekly_anchor' => 'friday',
                ]
            ]
        ]
    ]);

    // You may wish/need to refresh your billable model as it has been deleted and recreated
    $store->refresh();

    // Redirect to Stripe account onboarding, with return and refresh url, otherwise.
    return $store->redirectToAccountOnboarding(
        URL::to('/api/stripe/return?api_token=' . $store->api_token),
        URL::to('/api/stripe/refresh?api_token=' . $store->api_token)
    );
}

`

The two changes are:

  1. Swapping out hasStripeAccountId() for hasStripeAccount() as hasStripeAccountId() doesn't exist.
  2. Suggesting optionally the user may want to refresh their connect billable model to get the new account id correctly amongst other things

For point 2. around refreshing the billable model this could also be done by the package after the new mapping is created but I am wary of refreshing a model without the developer explicitly knowing but open to thoughts there on more seasoned users of this package?

Hope this helps, apologies if I've missed something I am new to the package.

Have a good one :)

ConnectCustomer + Billable Collision

Curious issue. The ConnectCustomer trait requires that the Billable trait is applied, but applying it to my model causes a collision.

From ConnectCustomer

     50▕             if(!in_array('Lanos\CashierConnect\Billable', $traits)){
  ➜  51▕                 throw new Exception('This model does not have a connect Billable trait on.');
     52▕             }

Exception:
Trait method Lanos\CashierConnect\ConnectCustomer::stripeAccountOptions has not been applied as App\Models\Customer::stripeAccountOptions, because of collision with Lanos\CashierConnect\Billable::stripeAccountOptions

Can anyone advise?

Error al pasar Fecha y Hora a payoutStripeAccount

Tengo un error al pasar el segundo parámetro de la función payoutStripeAccount, el correspondiente a la fecha. Me dice que Carbon\Traits\Date no es instanciable. Intento enviar una fecha creada con un objeto de Carbon y me dice que el parámetro que se espera es Carbon\Traits\Date claro está

Documentation typos

Hi Robert. Awesome looking package, and I can't wait to dive into it.

The documentation has a bunch of small typos in it. Would you be open to having someone help fix those or would you prefer me to list them all in this issue?

Cashier requirement on installation

Hi,

Laravel cashier has been updated to 14.6, which means that Composer no longer supports the installation :

Problem 1
- Root composer.json requires lanos/laravel-cashier-stripe-connect ^1.0 -> satisfiable by lanos/laravel-cashier-stripe-connect[1.0.5, ..., 1.0.10].
- lanos/laravel-cashier-stripe-connect[1.0.5, ..., 1.0.10] require laravel/cashier ^12.6|^13.4 -> found laravel/cashier[v12.6.0, ..., 12.x-dev, v13.4.0, ..., 13.x-dev] but it conflicts with your root composer.json require (^14.6).

Calling getPaymentMethods() on a connected customer, throws an error on stripe side

I have a Model which is using the ConnectCustomer trait, when I am calling the getPaymentMethods() on an instance of that Model, it is throwing this error
"Options found in $params: api_key, stripe_account. Options should be passed in their own array after $params. (HINT: pass an empty array to $params if you do not have any.)"

Trace:

[
{
"function": "handleError",
"class": "Illuminate\Foundation\Bootstrap\HandleExceptions",
"type": "->"
},
{
"file": "/var/www/html/vendor/stripe/stripe-php/lib/ApiRequestor.php",
"line": 389,
"function": "trigger_error"
},
{
"file": "/var/www/html/vendor/stripe/stripe-php/lib/ApiRequestor.php",
"line": 447,
"function": "_prepareRequest",
"class": "Stripe\ApiRequestor",
"type": "->"
},
{
"file": "/var/www/html/vendor/stripe/stripe-php/lib/ApiRequestor.php",
"line": 123,
"function": "_requestRaw",
"class": "Stripe\ApiRequestor",
"type": "->"
},
{
"file": "/var/www/html/vendor/stripe/stripe-php/lib/ApiOperations/Request.php",
"line": 78,
"function": "request",
"class": "Stripe\ApiRequestor",
"type": "->"
},
{
"file": "/var/www/html/vendor/stripe/stripe-php/lib/Customer.php",
"line": 99,
"function": "_staticRequest",
"class": "Stripe\ApiResource",
"type": "::"
},
{
"file": "/var/www/html/vendor/lanos/laravel-cashier-stripe-connect/src/Concerns/ManageConnectedPaymentMethods.php",
"line": 25,
"function": "allPaymentMethods",
"class": "Stripe\Customer",
"type": "::"
},
{
"file": "/var/www/html/app/Http/Controllers/API/v1/ContractController.php",
"line": 362,
"function": "getPaymentMethods",
"class": "App\Models\User",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Controller.php",
"line": 54,
"function": "respond",
"class": "App\Http\Controllers\API\v1\ContractController",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php",
"line": 45,
"function": "callAction",
"class": "Illuminate\Routing\Controller",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Route.php",
"line": 262,
"function": "dispatch",
"class": "Illuminate\Routing\ControllerDispatcher",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Route.php",
"line": 205,
"function": "runController",
"class": "Illuminate\Routing\Route",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
"line": 721,
"function": "run",
"class": "Illuminate\Routing\Route",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 128,
"function": "Illuminate\Routing\{closure}",
"class": "Illuminate\Routing\Router",
"type": "->"
},
{
"file": "/var/www/html/app/Http/Middleware/Localization.php",
"line": 25,
"function": "Illuminate\Pipeline\{closure}",
"class": "Illuminate\Pipeline\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 167,
"function": "handle",
"class": "App\Http\Middleware\Localization",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php",
"line": 50,
"function": "Illuminate\Pipeline\{closure}",
"class": "Illuminate\Pipeline\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 167,
"function": "handle",
"class": "Illuminate\Routing\Middleware\SubstituteBindings",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php",
"line": 127,
"function": "Illuminate\Pipeline\{closure}",
"class": "Illuminate\Pipeline\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php",
"line": 103,
"function": "handleRequest",
"class": "Illuminate\Routing\Middleware\ThrottleRequests",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php",
"line": 55,
"function": "handleRequestUsingNamedLimiter",
"class": "Illuminate\Routing\Middleware\ThrottleRequests",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 167,
"function": "handle",
"class": "Illuminate\Routing\Middleware\ThrottleRequests",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php",
"line": 44,
"function": "Illuminate\Pipeline\{closure}",
"class": "Illuminate\Pipeline\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 167,
"function": "handle",
"class": "Illuminate\Auth\Middleware\Authenticate",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/sanctum/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php",
"line": 33,
"function": "Illuminate\Pipeline\{closure}",
"class": "Illuminate\Pipeline\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 128,
"function": "Laravel\Sanctum\Http\Middleware\{closure}",
"class": "Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 103,
"function": "Illuminate\Pipeline\{closure}",
"class": "Illuminate\Pipeline\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/sanctum/src/Http/Middleware/EnsureFrontendRequestsAreStateful.php",
"line": 34,
"function": "then",
"class": "Illuminate\Pipeline\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 167,
"function": "handle",
"class": "Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 103,
"function": "Illuminate\Pipeline\{closure}",
"class": "Illuminate\Pipeline\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
"line": 723,
"function": "then",
"class": "Illuminate\Pipeline\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
"line": 698,
"function": "runRouteWithinStack",
"class": "Illuminate\Routing\Router",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
"line": 662,
"function": "runRoute",
"class": "Illuminate\Routing\Router",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php",
"line": 651,
"function": "dispatchToRoute",
"class": "Illuminate\Routing\Router",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php",
"line": 167,
"function": "dispatch",
"class": "Illuminate\Routing\Router",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 128,
"function": "Illuminate\Foundation\Http\{closure}",
"class": "Illuminate\Foundation\Http\Kernel",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php",
"line": 49,
"function": "Illuminate\Pipeline\{closure}",
"class": "Illuminate\Pipeline\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 167,
"function": "handle",
"class": "Illuminate\View\Middleware\ShareErrorsFromSession",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php",
"line": 121,
"function": "Illuminate\Pipeline\{closure}",
"class": "Illuminate\Pipeline\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php",
"line": 64,
"function": "handleStatefulRequest",
"class": "Illuminate\Session\Middleware\StartSession",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 167,
"function": "handle",
"class": "Illuminate\Session\Middleware\StartSession",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php",
"line": 21,
"function": "Illuminate\Pipeline\{closure}",
"class": "Illuminate\Pipeline\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php",
"line": 31,
"function": "handle",
"class": "Illuminate\Foundation\Http\Middleware\TransformsRequest",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 167,
"function": "handle",
"class": "Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php",
"line": 21,
"function": "Illuminate\Pipeline\{closure}",
"class": "Illuminate\Pipeline\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php",
"line": 40,
"function": "handle",
"class": "Illuminate\Foundation\Http\Middleware\TransformsRequest",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 167,
"function": "handle",
"class": "Illuminate\Foundation\Http\Middleware\TrimStrings",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php",
"line": 27,
"function": "Illuminate\Pipeline\{closure}",
"class": "Illuminate\Pipeline\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 167,
"function": "handle",
"class": "Illuminate\Foundation\Http\Middleware\ValidatePostSize",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php",
"line": 86,
"function": "Illuminate\Pipeline\{closure}",
"class": "Illuminate\Pipeline\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 167,
"function": "handle",
"class": "Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance",
"type": "->"
},
{
"file": "/var/www/html/vendor/fruitcake/laravel-cors/src/HandleCors.php",
"line": 52,
"function": "Illuminate\Pipeline\{closure}",
"class": "Illuminate\Pipeline\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 167,
"function": "handle",
"class": "Fruitcake\Cors\HandleCors",
"type": "->"
},
{
"file": "/var/www/html/vendor/fideloper/proxy/src/TrustProxies.php",
"line": 57,
"function": "Illuminate\Pipeline\{closure}",
"class": "Illuminate\Pipeline\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 167,
"function": "handle",
"class": "Fideloper\Proxy\TrustProxies",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php",
"line": 103,
"function": "Illuminate\Pipeline\{closure}",
"class": "Illuminate\Pipeline\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php",
"line": 142,
"function": "then",
"class": "Illuminate\Pipeline\Pipeline",
"type": "->"
},
{
"file": "/var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php",
"line": 111,
"function": "sendRequestThroughRouter",
"class": "Illuminate\Foundation\Http\Kernel",
"type": "->"
},
{
"file": "/var/www/html/public/index.php",
"line": 52,
"function": "handle",
"class": "Illuminate\Foundation\Http\Kernel",
"type": "->"
},
{
"file": "/var/www/html/server.php",
"line": 21,
"function": "require_once"
}
]

Cashier Requirement Installation

Here is what I see in Laravel 10 project. With cashier just installed:

Problem 1
- lanos/laravel-cashier-stripe-connect[1.0.5, ..., 1.0.10] require laravel/cashier ^12.6|^13.4 -> found laravel/cashier[v12.6.0, ..., v12.17.2, v13.4.0, ..., v13.17.0] but it conflicts with your root composer.json require (^14.9).
- lanos/laravel-cashier-stripe-connect[v1.0.11, ..., v1.0.13] require illuminate/console ^8.37|^9.0 -> found illuminate/console[v8.37.0, ..., v8.83.27, v9.0.0, ..., v9.52.4] but these were not loaded, likely because it conflicts with another require.
- Root composer.json requires lanos/laravel-cashier-stripe-connect * -> satisfiable by lanos/laravel-cashier-stripe-connect[1.0.5, ..., v1.0.13].

Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions.
You can also try re-running composer require with an explicit version constraint, e.g. "composer require lanos/laravel-cashier-stripe-connect:*" to figure out if any version is installable, or "composer require lanos/laravel-cashier-stripe-connect:^2.1" if you know which you need.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.