├── .styleci.yml
├── src
├── Openpay
│ ├── Card.php
│ ├── Plan.php
│ ├── Webhook.php
│ ├── Customer.php
│ ├── BankAccount.php
│ ├── Subscription.php
│ ├── Charge.php
│ └── Base.php
├── OpenpayInstance.php
├── Cashier.php
├── Http
│ └── Controllers
│ │ ├── BaseWebhookController.php
│ │ └── WebhookController.php.stub
├── BankAccount.php
├── Card.php
├── CashierOpenpayServiceProvider.php
├── Subscription.php
├── OpenpayExceptionsHandler.php
└── Billable.php
├── todo.md
├── license.md
├── changelog.md
├── .gitignore
├── resources
├── views
│ ├── js_load.blade.php
│ ├── js_jquery.blade.php
│ └── js.blade.php
└── lang
│ ├── en.json
│ └── es.json
├── routes
└── webhook.php
├── tests
├── Fixtures
│ ├── redirect_localhost.html
│ └── User.php
├── database
│ └── migrations
│ │ └── create_users_table.php
├── Http
│ └── Controllers
│ │ └── WebhookControllerTest.php
├── SubscriptionTest.php
├── BillableTest.php
├── BaseTestCase.php
└── Traits
│ └── OpenpayExceptionHandlerTest.php
├── .travis.yml
├── database
└── migrations
│ ├── create_customer_columns.php.stub
│ ├── create_bank_accounts_table.php.stub
│ ├── create_subscriptions_table.php.stub
│ └── create_cards_table.php.stub
├── contributing.md
├── phpunit.xml.dist
├── composer.json
├── config
└── cashier_openpay.php
└── readme.md
/.styleci.yml:
--------------------------------------------------------------------------------
1 | preset: laravel
2 | finder:
3 | exclude:
4 | - "tests/database/migrations"
5 |
--------------------------------------------------------------------------------
/src/Openpay/Card.php:
--------------------------------------------------------------------------------
1 |
4 |
5 | ...Add your license text here...
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to `CashierOpenpay` will be documented in this file.
4 |
5 | ## Version 1.0
6 |
7 | ### Added
8 | - Everything
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | composer.lock
3 | docs
4 | vendor
5 | coverage
6 | phpunit.xml
7 | .phpunit.result.cache
8 | .idea
9 | .phpstorm.meta.php
10 | .php_cs.cache
11 |
--------------------------------------------------------------------------------
/resources/views/js_load.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/routes/webhook.php:
--------------------------------------------------------------------------------
1 | post(
7 | Cashier::webhookUrl(),
8 | Cashier::webhookController().'@'.Cashier::webhookMethod()
9 | );
10 |
--------------------------------------------------------------------------------
/resources/views/js_jquery.blade.php:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/resources/views/js.blade.php:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/tests/Fixtures/redirect_localhost.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Redirecting to http://localhost
8 |
9 |
10 | Redirecting to http://localhost.
11 |
12 |
13 |
--------------------------------------------------------------------------------
/tests/Fixtures/User.php:
--------------------------------------------------------------------------------
1 | refund($data);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | sudo: false
4 |
5 | php:
6 | - 7.2
7 | - 7.3
8 | - 7.4
9 |
10 | env:
11 | matrix:
12 | - LARAVEL='6.0.*' TESTBENCH='4.0.*'
13 | - LARAVEL='7.0.*' TESTBENCH='5.0.*'
14 |
15 | cache:
16 | directories:
17 | - "$HOME/.composer/cache"
18 |
19 | matrix:
20 | fast_finish: true
21 |
22 | before_script: composer config discard-changes true
23 |
24 | before_install:
25 | - travis_retry composer self-update
26 | - travis_retry composer require "illuminate/support:${LARAVEL}" "orchestra/testbench:${TESTBENCH}" --no-interaction --no-update
27 |
28 | install: travis_retry composer install --prefer-dist --no-interaction --no-suggest
29 |
30 | script: vendor/bin/phpunit
31 |
32 |
--------------------------------------------------------------------------------
/database/migrations/create_customer_columns.php.stub:
--------------------------------------------------------------------------------
1 | string('openpay_id')->nullable()->index();
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function down()
27 | {
28 | Schema::table('users', function (Blueprint $table) {
29 | $table->dropColumn([
30 | 'openpay_id',
31 | ]);
32 | });
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/OpenpayInstance.php:
--------------------------------------------------------------------------------
1 | increments('id');
20 | $table->string('name');
21 | $table->string('email')->unique();
22 | $table->timestamp('email_verified_at')->nullable();
23 | $table->string('password');
24 | $table->rememberToken();
25 | $table->timestamps();
26 | });
27 | }
28 |
29 | /**
30 | * Reverse the migrations.
31 | *
32 | * @return void
33 | */
34 | public function down()
35 | {
36 | Schema::dropIfExists('users');
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/database/migrations/create_bank_accounts_table.php.stub:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->unsignedBigInteger('user_id');
19 |
20 | $table->string('openpay_id')->index();
21 | $table->string('holder_name');
22 | $table->string('clabe');
23 | $table->string('bank_name');
24 | $table->string('bank_code');
25 |
26 | $table->string('alias')->nullable();
27 | $table->timestamps();
28 | });
29 | }
30 |
31 | /**
32 | * Reverse the migrations.
33 | *
34 | * @return void
35 | */
36 | public function down()
37 | {
38 | Schema::dropIfExists('bank_accounts');
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Http/Controllers/BaseWebhookController.php:
--------------------------------------------------------------------------------
1 | getContent(), true);
20 | $method = 'handle'.Str::studly(str_replace('.', '_', $payload['type']));
21 |
22 | if (method_exists($this, $method)) {
23 | return $this->{$method}($payload);
24 | }
25 |
26 | return $this->missingMethod();
27 | }
28 |
29 | /**
30 | * Handle calls to missing methods on the controller.
31 | *
32 | * @param array $parameters
33 | * @return \Symfony\Component\HttpFoundation\Response
34 | */
35 | public function missingMethod($parameters = [])
36 | {
37 | return new Response;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/database/migrations/create_subscriptions_table.php.stub:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->unsignedBigInteger('user_id');
19 |
20 | $table->string('openpay_id')->index();
21 | $table->string('name');
22 | $table->string('openpay_status');
23 | $table->string('openpay_plan');
24 | $table->timestamp('trial_ends_at')->nullable();
25 | $table->timestamp('ends_at')->nullable();
26 | $table->timestamps();
27 | });
28 | }
29 |
30 | /**
31 | * Reverse the migrations.
32 | *
33 | * @return void
34 | */
35 | public function down()
36 | {
37 | Schema::dropIfExists('subscriptions');
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/database/migrations/create_cards_table.php.stub:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->unsignedBigInteger('user_id');
19 |
20 | $table->string('openpay_id')->index();
21 | $table->string('type');
22 | $table->string('brand')->nullable();
23 | $table->string('holder_name');
24 | $table->string('card_number');
25 | $table->string('expiration_month', 2);
26 | $table->string('expiration_year', 2);
27 | $table->string('bank_name');
28 | $table->string('bank_code');
29 |
30 | $table->timestamps();
31 | });
32 | }
33 |
34 | /**
35 | * Reverse the migrations.
36 | *
37 | * @return void
38 | */
39 | public function down()
40 | {
41 | Schema::dropIfExists('cards');
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are welcome and will be fully credited.
4 |
5 | Contributions are accepted via Pull Requests on [Github](https://github.com/perafan/cashieropenpay).
6 |
7 | # Things you could do
8 | If you want to contribute but do not know where to start, this list provides some starting points.
9 | - Add license text
10 | - Remove rewriteRules.php
11 | - Set up TravisCI, StyleCI, ScrutinizerCI
12 | - Write a comprehensive ReadMe
13 |
14 | ## Pull Requests
15 |
16 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests.
17 |
18 | - **Document any change in behaviour** - Make sure the `readme.md` and any other relevant documentation are kept up-to-date.
19 |
20 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option.
21 |
22 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
23 |
24 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.
25 |
26 |
27 | **Happy coding**!
28 |
--------------------------------------------------------------------------------
/src/BankAccount.php:
--------------------------------------------------------------------------------
1 | owner();
22 | }
23 |
24 | /**
25 | * Get the model related to the subscription.
26 | *
27 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
28 | */
29 | public function owner()
30 | {
31 | $model = config('cashier_openpay.model');
32 |
33 | return $this->belongsTo($model, (new $model)->getForeignKey());
34 | }
35 |
36 | /**
37 | * Get the subscription as a Openpay bank account object.
38 | *
39 | * @return \OpenpayBankAccount
40 | */
41 | public function asOpenpayBankAccount()
42 | {
43 | $customer = $this->owner->asOpenpayCustomer();
44 |
45 | return OpenpayBankAccount::find($this->openpay_id, $customer);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Card.php:
--------------------------------------------------------------------------------
1 | owner();
23 | }
24 |
25 | /**
26 | * Get the model related to the subscription.
27 | *
28 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
29 | */
30 | public function owner()
31 | {
32 | $model = config('cashier_openpay.model');
33 |
34 | return $this->belongsTo($model, (new $model)->getForeignKey());
35 | }
36 |
37 | /**
38 | * Get the subscription as a Openpay card object.
39 | *
40 | * @return \OpenpayCard
41 | */
42 | public function asOpenpayCard()
43 | {
44 | $customer = $this->owner->asOpenpayCustomer();
45 |
46 | return OpenpayCard::find($this->openpay_id, $customer);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 | tests
16 |
17 |
18 |
19 |
20 | src/
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "perafan/cashier-openpay",
3 | "description": "Laravel Cashier provides an expressive, fluent interface to Openpay's subscription billing services.",
4 | "keywords": ["laravel", "billing", "cashier", "openpay"],
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "Pedro Perafán Carrasco",
9 | "email": "pedro.perafan.carrasco@gmail.com"
10 | }
11 | ],
12 | "homepage": "https://github.com/Perafan18/cashier-openpay",
13 | "require": {
14 | "illuminate/support": "~6|~7|~8",
15 | "openpay/sdk": "dev-master",
16 | "ext-json": "*"
17 | },
18 | "require-dev": {
19 | "phpunit/phpunit": "^8.0",
20 | "mockery/mockery": "^1.1",
21 | "orchestra/testbench": "~3|~4",
22 | "sempro/phpunit-pretty-print": "^1.0"
23 | },
24 | "autoload": {
25 | "psr-4": {
26 | "Perafan\\CashierOpenpay\\": "src/"
27 | }
28 | },
29 | "autoload-dev": {
30 | "psr-4": {
31 | "Perafan\\CashierOpenpay\\Tests\\": "tests/"
32 | }
33 | },
34 | "extra": {
35 | "laravel": {
36 | "providers": [
37 | "Perafan\\CashierOpenpay\\CashierOpenpayServiceProvider"
38 | ],
39 | "aliases": {
40 | "CashierOpenpay": "Perafan\\CashierOpenpay\\Facades\\CashierOpenpay"
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tests/Http/Controllers/WebhookControllerTest.php:
--------------------------------------------------------------------------------
1 | request('/', 'POST', ['type' => 'charge.succeeded']);
14 |
15 | $response = (new WebhookControllerTestStub)->handleWebhook($request);
16 |
17 | $this->assertEquals('Webhook Charge Succeeded', $response->getContent());
18 |
19 | $this->assertEquals(200, $response->getStatusCode());
20 | }
21 |
22 | public function testVerificationAndMethodExists()
23 | {
24 | $request = $this->request('/', 'POST', ['type' => 'verification']);
25 |
26 | $response = (new WebhookControllerTestStub)->handleWebhook($request);
27 |
28 | $this->assertEquals('Webhook Verification', $response->getContent());
29 |
30 | $this->assertEquals(200, $response->getStatusCode());
31 | }
32 |
33 | public function testRandomEventAndTheMethodDoesntExists()
34 | {
35 | $request = $this->request('/', 'POST', ['type' => 'random.event']);
36 |
37 | $response = (new WebhookControllerTestStub)->handleWebhook($request);
38 |
39 | $this->assertEquals('', $response->getContent());
40 | }
41 | }
42 |
43 | class WebhookControllerTestStub extends BaseWebhookController
44 | {
45 | protected function handleChargeSucceeded()
46 | {
47 | return new Response('Webhook Charge Succeeded', 200);
48 | }
49 |
50 | protected function handleVerification()
51 | {
52 | return new Response('Webhook Verification', 200);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Openpay/Base.php:
--------------------------------------------------------------------------------
1 | {self::resource()}->get($id);
20 | }
21 |
22 | return $customer->{self::resource()}->get($id);
23 | }
24 |
25 | /**
26 | * @param array $options
27 | * @param OpenpayCustomer|null $customer
28 | * @return mixed
29 | */
30 | public static function create(array $options = [], OpenpayCustomer $customer = null)
31 | {
32 | if (is_null($customer)) {
33 | return OpenpayInstance::getInstance()->{self::resource()}->create($options);
34 | }
35 |
36 | return $customer->{self::resource()}->create($options);
37 | }
38 |
39 | /**
40 | * @param array $options
41 | * @param OpenpayCustomer|null $customer
42 | * @return mixed
43 | */
44 | public static function add(array $options = [], OpenpayCustomer $customer = null)
45 | {
46 | if (is_null($customer)) {
47 | return OpenpayInstance::getInstance()->{self::resource()}->add($options);
48 | }
49 |
50 | return $customer->{self::resource()}->add($options);
51 | }
52 |
53 | /**
54 | * @param $id
55 | * @param OpenpayCustomer|null $customer
56 | * @return mixed
57 | */
58 | public static function delete($id, OpenpayCustomer $customer = null)
59 | {
60 | $resource_object = self::find($id, $customer);
61 |
62 | return $resource_object->delete();
63 | }
64 |
65 | /**
66 | * @param array $options
67 | * @param OpenpayCustomer $customer
68 | * @return mixed
69 | */
70 | public static function all(array $options = [], OpenpayCustomer $customer = null)
71 | {
72 | if (is_null($customer)) {
73 | return OpenpayInstance::getInstance()->{self::resource()}->getList($options);
74 | }
75 |
76 | return $customer->{self::resource()}->getList($options);
77 | }
78 |
79 | /**
80 | * @return string
81 | */
82 | protected static function resource()
83 | {
84 | $klass = preg_replace('/Perafan|CashierOpenpay|Openpay|\\\\/', '', get_called_class());
85 | $klass = Str::lower($klass);
86 |
87 | return Str::plural($klass);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/config/cashier_openpay.php:
--------------------------------------------------------------------------------
1 | env('OPENPAY_PRODUCTION_MODE', false),
15 |
16 | /*
17 | |--------------------------------------------------------------------------
18 | | Openpay API Keys
19 | |--------------------------------------------------------------------------
20 | |
21 | | You can retrieve your Openpay API keys from the Openpay control panel.
22 | |
23 | */
24 |
25 | 'id' => env('OPENPAY_ID', ''),
26 |
27 | 'private_key' => env('OPENPAY_PRIVATE_KEY', ''),
28 |
29 | 'public_key' => env('OPENPAY_PUBLIC_KEY', ''),
30 |
31 | /*
32 | |--------------------------------------------------------------------------
33 | | Log Errors
34 | |--------------------------------------------------------------------------
35 | |
36 | | Set as true if you want to see the data from Openpay exceptions (HTTP
37 | | requests) in your laravel.log
38 | | Note: You need to use OpenpayExceptionsHandler
39 | */
40 |
41 | 'log_errors' => env('OPENPAY_LOG_ERRORS', false),
42 |
43 | /*
44 | |--------------------------------------------------------------------------
45 | | Cashier Model
46 | |--------------------------------------------------------------------------
47 | |
48 | | This is the model in your application that implements the Billable trait
49 | | provided by Cashier. It will serve as the primary model you use while
50 | | interacting with Cashier related methods, subscriptions, and so on.
51 | |
52 | */
53 |
54 | 'model' => env('OPENPAY_MODEL', App\Models\User::class),
55 |
56 | /*
57 | |--------------------------------------------------------------------------
58 | | Webhook
59 | |--------------------------------------------------------------------------
60 | |
61 | | Cashier Openpay provides a WebhookController with the methods to add your
62 | | own business logic.
63 | | Note: First you need to publish the tag "cashier-openpay-webhook-controller"
64 | |
65 | | You may change the url of the webhook, the route name, the controller path
66 | | or maybe the method.
67 | |
68 | */
69 |
70 | 'webhook' => [
71 |
72 | 'route_name' => env('OPENPAY_WEBHOOK_ROUTE', 'openpay.webhooks.handle'),
73 |
74 | 'url' => env('OPENPAY_WEBHOOK_URL', 'openpay/webhooks/handle'),
75 |
76 | 'controller' => env('OPENPAY_WEBHOOK_CONTROLLER', '\App\Http\Controllers\WebhookController'),
77 |
78 | 'method' => env('OPENPAY_WEBHOOK_METHOD', 'handleWebhook'),
79 | ],
80 | ];
81 |
--------------------------------------------------------------------------------
/tests/SubscriptionTest.php:
--------------------------------------------------------------------------------
1 | withPackageMigrations();
21 | self::$user = $this->createUser();
22 |
23 | self::$user->createAsOpenpayCustomer([
24 | 'external_id' => $this->randomExternalId(),
25 | ]);
26 | }
27 |
28 | public function tearDown(): void
29 | {
30 | self::$user->asOpenpayCustomer()->delete();
31 |
32 | parent::tearDown();
33 | }
34 |
35 | /** @test */
36 | public function testOnGracePeriod()
37 | {
38 | $plan = $this->createPlan();
39 |
40 | $subscription_data = [
41 | 'card' => $this->cardData(),
42 | ];
43 |
44 | $subscription = self::$user->newSubscription($plan->id, $subscription_data);
45 |
46 | $this->assertTrue($subscription->hasPlan($plan->id));
47 |
48 | $this->assertFalse($subscription->onGracePeriod());
49 |
50 | $this->assertTrue($subscription->onTrial());
51 |
52 | $this->assertFalse($subscription->pastDue());
53 |
54 | $this->assertFalse($subscription->unpaid());
55 |
56 | $this->assertFalse($subscription->ended());
57 |
58 | $this->assertEquals(self::$user->id, $subscription->owner->id);
59 |
60 | $this->assertFalse($subscription->cancelled());
61 |
62 | $subscription->cancel();
63 |
64 | $this->assertTrue($subscription->cancelled());
65 |
66 | $this->assertTrue($subscription->onGracePeriod());
67 |
68 | $subscription->cancelNow();
69 |
70 | Plan::delete($plan->id);
71 | }
72 |
73 | /** @test */
74 | public function testOnGracePeriods()
75 | {
76 | $plan = $this->createPlan();
77 |
78 | $subscription_data = [
79 | 'card' => $this->cardData(),
80 | 'trial_end_date' => Carbon::now()->subDay(),
81 | ];
82 |
83 | $subscription = self::$user->newSubscription($plan->id, $subscription_data);
84 |
85 | $this->assertTrue($subscription->hasPlan($plan->id));
86 |
87 | $this->assertFalse($subscription->onGracePeriod());
88 |
89 | $this->assertFalse($subscription->onTrial());
90 |
91 | $this->assertFalse($subscription->pastDue());
92 |
93 | $this->assertFalse($subscription->unpaid());
94 |
95 | $this->assertFalse($subscription->ended());
96 |
97 | $this->assertEquals(self::$user->id, $subscription->owner->id);
98 |
99 | $this->assertFalse($subscription->cancelled());
100 |
101 | $subscription->cancelNow();
102 |
103 | $this->assertFalse($subscription->onGracePeriod());
104 |
105 | $this->assertTrue($subscription->cancelled());
106 |
107 | Plan::delete($plan->id);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/CashierOpenpayServiceProvider.php:
--------------------------------------------------------------------------------
1 | mergeConfigFrom(__DIR__.'/../config/cashier_openpay.php', 'cashier_openpay');
18 | }
19 |
20 | /**
21 | * Perform post-registration booting of services.
22 | *
23 | * @return void
24 | */
25 | public function boot()
26 | {
27 | $this->bootRoutes();
28 | $this->bootResources();
29 | $this->bootDirectives();
30 | $this->bootPublishing();
31 | }
32 |
33 | /**
34 | * Boot the package resources.
35 | *
36 | * @return void
37 | */
38 | protected function bootResources()
39 | {
40 | $this->loadViewsFrom(__DIR__.'/../resources/views', 'cashier_openpay');
41 | }
42 |
43 | /**
44 | * Boot the package routes.
45 | *
46 | * @return void
47 | */
48 | protected function bootRoutes()
49 | {
50 | $this->loadRoutesFrom(__DIR__.'/../routes/webhook.php');
51 | }
52 |
53 | /**
54 | * Boot the package directives.
55 | *
56 | * @return void
57 | */
58 | protected function bootDirectives()
59 | {
60 | Blade::directive('openpayJSLoad', function () {
61 | return "";
62 | });
63 |
64 | Blade::directive('openpayJqueryJSInit', function () {
65 | return "";
66 | });
67 |
68 | Blade::directive('openpayJSInit', function () {
69 | return "";
70 | });
71 | }
72 |
73 | /**
74 | * Boot the package's migrations.
75 | *
76 | * @return void
77 | */
78 | protected function bootPublishingMigrations()
79 | {
80 | $prefix = 'migrations/'.date('Y_m_d_His', time());
81 |
82 | $this->publishes([
83 | __DIR__.'/../database/migrations/create_customer_columns.php.stub' => database_path($prefix.'_create_customer_columns.php'),
84 | __DIR__.'/../database/migrations/create_subscriptions_table.php.stub' => database_path($prefix.'_create_subscriptions_table.php'),
85 | __DIR__.'/../database/migrations/create_cards_table.php.stub' => database_path($prefix.'_create_cards_table.php'),
86 | __DIR__.'/../database/migrations/create_bank_accounts_table.php.stub' => database_path($prefix.'_create_bank_accounts_table.php'),
87 | ], 'cashier-openpay-migrations');
88 | }
89 |
90 | /**
91 | * Boot the package's controller.
92 | *
93 | * @return void
94 | */
95 | protected function bootPublishingController()
96 | {
97 | $this->publishes([
98 | __DIR__.'/Http/Controllers/WebhookController.php.stub' => app_path('Http/Controllers/WebhookController.php'),
99 | ], 'cashier-openpay-webhook-controller');
100 | }
101 |
102 | /**
103 | * Boot the package's config file.
104 | *
105 | * @return void
106 | */
107 | protected function bootPublishingConfig()
108 | {
109 | $this->publishes([
110 | __DIR__.'/../config/cashier_openpay.php' => config_path('cashier_openpay.php'),
111 | ], 'cashier-openpay-configs');
112 | }
113 |
114 | /**
115 | * Boot the package's publishable resources.
116 | *
117 | * @return void
118 | */
119 | protected function bootPublishing()
120 | {
121 | if ($this->app->runningInConsole()) {
122 | $this->bootPublishingConfig();
123 |
124 | $this->bootPublishingController();
125 |
126 | $this->bootPublishingMigrations();
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/resources/lang/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "Internal server error, contact support": "Internal server error, contact support.",
3 | "Bad Request": "Bad Request.",
4 | "The api key or merchant id are invalid": "The api key or merchant id are invalid.",
5 | "Parameters look valid but request failed": "Parameters look valid but request failed.",
6 | "The resource is unavailable at this moment. Please try again later": "The resource is unavailable at this moment. Please try again later.",
7 | "The requested resource doesn't exist": "The requested resource doesn't exist.",
8 | "The order_id has already been processed": "The order_id has already been processed.",
9 | "Operation rejected by processor": "Operation rejected by processor.",
10 | "The account is inactive": "The account is inactive.",
11 | "The request is too large": "The request is too large.",
12 | "Method not allowed for public API key, use private key instead": "Method not allowed for public API key, use private key instead.",
13 | "The resource was previously deleted": "The resource was previously deleted.",
14 | "The transaction amount exceeds your allowed transaction limit": "The transaction amount exceeds your allowed transaction limit.",
15 | "The operation is not allowed on the resource": "The operation is not allowed on the resource.",
16 | "Your account is inactive, please contact to soporte@openpay.mx for more information": "Your account is inactive, please contact to soporte@openpay.mx for more information.",
17 | "Could not get any response from gateway. Please try again later": "Could not get any response from gateway. Please try again later.",
18 | "The merchant email has been already processed": "The merchant email has been already processed.",
19 | "The payment gateway is not available at the moment, please try again later": "The payment gateway is not available at the moment, please try again later.",
20 | "The number of retries of charge is greater than allowed": "The number of retries of charge is greater than allowed.",
21 | "The number of decimal digits is not valid for this currency": "The number of decimal digits is not valid for this currency.",
22 | "The bank account already exists": "The bank account already exists.",
23 | "The external_id already exists": "The external_id already exists.",
24 | "The card number verification digit is invalid": "The card number verification digit is invalid.",
25 | "The expiration date has expired": "The expiration date has expired.",
26 | "The CVV2 security code is required": "The CVV2 security code is required.",
27 | "The card number is only valid in sandbox": "The card number is only valid in sandbox.",
28 | "The card is not valid for points": "The card is not valid for points.",
29 | "The CVV2 security code is invalid": "The CVV2 security code is invalid.",
30 | "3D Secure authentication failed": "3D Secure authentication failed.",
31 | "Card product type not supported": "Card product type not supported.",
32 | "The card was declined by the bank": "The card was declined by the bank.",
33 | "The card has expired": "The card has expired.",
34 | "The card doesn't have sufficient funds": "The card doesn't have sufficient funds.",
35 | "The card was reported as stolen": "The card was reported as stolen.",
36 | "Fraud risk detected by anti-fraud system": "Fraud risk detected by anti-fraud system.",
37 | "Request not allowed": "Request not allowed.",
38 | "The card was reported as lost": "The card was reported as lost.",
39 | "The bank has restricted the card": "The bank has restricted the card.",
40 | "The bank has requested the card to be retained": "The bank has requested the card to be retained.",
41 | "Bank authorization is required for this charge": "Bank authorization is required for this charge.",
42 | "Merchant not authorized to use payment plan": "Merchant not authorized to use payment plan.",
43 | "Invalid promotion for such card type": "Invalid promotion for such card type.",
44 | "Transaction amount is less than minimum for promotion": "Transaction amount is less than minimum for promotion.",
45 | "Promotion not allowed": "Promotion not allowed.",
46 | "There are not enough funds in the openpay account": "There are not enough funds in the openpay account.",
47 | "The operation can't be completed until pending fees are paid": "The operation can't be completed until pending fees are paid.",
48 | "The webhook has already been processed": "The webhook has already been processed.",
49 | "Could not connect with webhook service, verify URL": "Could not connect with webhook service, verify URL.",
50 | "Service responded with an error on this moment. Please try again later": "Service responded with an error on this moment. Please try again later"
51 | }
52 |
--------------------------------------------------------------------------------
/tests/BillableTest.php:
--------------------------------------------------------------------------------
1 | withPackageMigrations();
20 |
21 | self::$user = $this->createUser();
22 |
23 | self::$user->createAsOpenpayCustomer([
24 | 'external_id' => $this->randomExternalId(),
25 | ]);
26 |
27 | self::$plan = $this->createPlan();
28 | }
29 |
30 | public function tearDown(): void
31 | {
32 | self::$user->asOpenpayCustomer()->delete();
33 |
34 | OpenpayPlan::delete(self::$plan->id);
35 |
36 | parent::tearDown();
37 | }
38 |
39 | /** @test */
40 | public function testOpenpayId()
41 | {
42 | $user = $this->createUser(['email' => 'random@email.com']);
43 |
44 | $this->assertFalse($user->hasOpenpayId());
45 | }
46 |
47 | /** @test */
48 | public function testCreateOpenpayCustomer()
49 | {
50 | $user = $this->createUser(['name' => 'Pedro Perafán', 'email' => 'random@email.com']);
51 |
52 | $external_id = $this->randomExternalId();
53 |
54 | $this->assertFalse($user->hasOpenpayId());
55 |
56 | $user->createAsOpenpayCustomer([
57 | 'external_id' => $external_id,
58 | ]);
59 |
60 | $this->assertTrue($user->hasOpenpayId());
61 |
62 | $customer = $user->asOpenpayCustomer();
63 |
64 | $this->assertIsObject($customer);
65 |
66 | $this->assertEquals('Pedro Perafán', $customer->name);
67 |
68 | $this->assertEquals($external_id, $customer->external_id);
69 |
70 | $user->asOpenpayCustomer()->delete();
71 | }
72 |
73 | /** @test */
74 | public function testNewCard()
75 | {
76 | $card = self::$user->addCard($this->cardData(), $this->addressData());
77 |
78 | $this->assertEquals('411111XXXXXX1111', $card->card_number);
79 |
80 | $openpayCard = $card->asOpenpayCard();
81 |
82 | $this->assertTrue($openpayCard->allows_charges);
83 |
84 | $this->assertTrue($openpayCard->allows_payouts);
85 | }
86 |
87 | /** @test */
88 | public function testCharge()
89 | {
90 | $options = [
91 | 'method' => 'bank_account',
92 | 'description' => 'Cargo con banco',
93 | ];
94 |
95 | $charge = self::$user->charge(100.00, $options);
96 |
97 | $this->assertEquals('in_progress', $charge->status);
98 | $this->assertEquals('charge', $charge->transaction_type);
99 | $this->assertEquals('bank_transfer', $charge->payment_method->type);
100 | }
101 |
102 | /** @test */
103 | public function testNewSubscription()
104 | {
105 | $card_data = $this->cardData();
106 |
107 | $address = $this->addressData();
108 |
109 | $card = self::$user->addCard($card_data, $address);
110 |
111 | $openpayCard = $card->asOpenpayCard();
112 |
113 | $subscription = self::$user->newSubscription(self::$plan->id, ['source_id' => $openpayCard->id]);
114 |
115 | $this->assertEquals(self::$plan->id, $subscription->openpay_plan);
116 |
117 | $this->assertEquals(Subscription::STATUS_TRIAL, $subscription->openpay_status);
118 |
119 | $this->assertTrue(self::$user->onTrial('default'));
120 |
121 | $this->assertFalse(self::$user->onTrial('test'));
122 |
123 | $subscription->cancelNow();
124 | }
125 |
126 | /** @test */
127 | public function HasSubscription()
128 | {
129 | $subscription_data = [
130 | 'card' => $this->cardData(),
131 | 'trial_end_date' => Carbon::now()->subDay(),
132 | ];
133 |
134 | $subscription = self::$user->newSubscription(self::$plan->id, $subscription_data, 'test');
135 |
136 | $this->assertEquals(Subscription::STATUS_ACTIVE, $subscription->openpay_status);
137 |
138 | $this->assertEquals($subscription->id, self::$user->subscription('test')->id);
139 |
140 | $this->assertFalse(self::$user->onTrial('test'));
141 |
142 | $this->assertTrue(self::$user->subscribed('test'));
143 |
144 | $this->assertTrue(self::$user->subscribed('test', self::$plan->id));
145 |
146 | $this->assertFalse(self::$user->subscribed('hello', self::$plan->id));
147 |
148 | $this->assertFalse(self::$user->subscribed('hello'));
149 |
150 | $subscription->cancelNow();
151 | }
152 |
153 | /** @test */
154 | public function checkIfBillableModel()
155 | {
156 | $subscription_data = [
157 | 'card' => $this->cardData(),
158 | 'trial_end_date' => Carbon::now()->subDay(),
159 | ];
160 |
161 | $subscription = self::$user->newSubscription(self::$plan->id, $subscription_data);
162 |
163 | $this->assertTrue(self::$user->subscribedToPlan(self::$plan->id, 'default'));
164 |
165 | $this->assertTrue(self::$user->onPlan(self::$plan->id));
166 |
167 | $subscription->cancelNow();
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/resources/lang/es.json:
--------------------------------------------------------------------------------
1 | {
2 | "Internal server error, contact support": "Ocurrió un error interno en el servidor de Openpay.",
3 | "Bad Request": "El formato de la petición no es JSON, los campos no tienen el formato correcto, o la petición no tiene campos que son requeridos.",
4 | "The api key or merchant id are invalid": "La llamada no esta autenticada o la autenticación es incorrecta.",
5 | "Parameters look valid but request failed": "La operación no se pudo completar por que el valor de uno o más de los parámetros no es correcto.",
6 | "The resource is unavailable at this moment. Please try again later": "Un servicio necesario para el procesamiento de la transacción no se encuentra disponible.",
7 | "The requested resource doesn't exist": "Uno de los recursos requeridos no existe.",
8 | "The order_id has already been processed": "Ya existe una transacción con el mismo ID de orden.",
9 | "Operation rejected by processor": "La transferencia de fondos entre una cuenta de banco o tarjeta y la cuenta de Openpay no fue aceptada.",
10 | "The account is inactive": "Una de las cuentas requeridas en la petición se encuentra desactivada.",
11 | "The request is too large": "El cuerpo de la petición es demasiado grande.",
12 | "Method not allowed for public API key, use private key instead": "Se esta utilizando la llave pública para hacer una llamada que requiere la llave privada, o bien, se esta usando la llave privada desde JavaScript.",
13 | "The resource was previously deleted": "Se solicita un recurso que esta marcado como eliminado.",
14 | "The transaction amount exceeds your allowed transaction limit": "El monto transacción esta fuera de los limites permitidos.",
15 | "The operation is not allowed on the resource": "La operación no esta permitida para el recurso.",
16 | "Your account is inactive, please contact to soporte@openpay.mx for more information": "La cuenta esta inactiva.",
17 | "Could not get any response from gateway. Please try again later": "No se ha obtenido respuesta de la solicitud realizada al servicio.",
18 | "The merchant email has been already processed": "El mail del comercio ya ha sido procesada.",
19 | "The payment gateway is not available at the moment, please try again later": "El gateway no se encuentra disponible en ese momento.",
20 | "The number of retries of charge is greater than allowed": "El número de intentos de cargo es mayor al permitido.",
21 | "The number of decimal digits is not valid for this currency": "El número de dígitos decimales es inválido para esta moneda.",
22 | "The bank account already exists": "La cuenta de banco con esta CLABE ya se encuentra registrada en el cliente.",
23 | "The external_id already exists": "El cliente con este identificador externo (External ID) ya existe.",
24 | "The card number verification digit is invalid": "El número de tarjeta es invalido.",
25 | "The expiration date has expired": "La fecha de expiración de la tarjeta es anterior a la fecha actual.",
26 | "The CVV2 security code is required": "El código de seguridad de la tarjeta (CVV2) no fue proporcionado.",
27 | "The card number is only valid in sandbox": "El número de tarjeta es de prueba, solamente puede usarse en Sandbox.",
28 | "The card is not valid for points": "La tarjeta no es valida para pago con puntos.",
29 | "The CVV2 security code is invalid": "El código de seguridad de la tarjeta (CVV2) es inválido.",
30 | "3D Secure authentication failed": "Autenticación 3D Secure fallida.",
31 | "Card product type not supported": "Tipo de tarjeta no soportada.",
32 | "The card was declined by the bank": "La tarjeta fue declinada por el banco.",
33 | "The card has expired": "La tarjeta ha expirado.",
34 | "The card doesn't have sufficient funds": "La tarjeta no tiene fondos suficientes.",
35 | "The card was reported as stolen": "La tarjeta ha sido identificada como una tarjeta robada.",
36 | "Fraud risk detected by anti-fraud system": "La tarjeta ha sido rechazada por el sistema antifraude.",
37 | "Request not allowed": "La operación no esta permitida para este cliente o esta transacción.",
38 | "The card was reported as lost": "La tarjeta fue reportada como perdida.",
39 | "The bank has restricted the card": "El banco ha restringido la tarjeta.",
40 | "The bank has requested the card to be retained": "El banco ha solicitado que la tarjeta sea retenida. Contacte al banco.",
41 | "Bank authorization is required for this charge": "Se requiere solicitar al banco autorización para realizar este pago.",
42 | "Merchant not authorized to use payment plan": "Comercio no autorizado para procesar pago a meses sin intereses.",
43 | "Invalid promotion for such card type": "Promoción no valida para este tipo de tarjetas.",
44 | "Transaction amount is less than minimum for promotion": "El monto de la transacción es menor al mínimo permitido para la promoción.",
45 | "Promotion not allowed": "Promoción no permitida.",
46 | "There are not enough funds in the openpay account": "La cuenta de Openpay no tiene fondos suficientes.",
47 | "The operation can't be completed until pending fees are paid": "La operación no puede ser completada hasta que sean pagadas las comisiones pendientes.",
48 | "The webhook has already been processed": "El webhook ya ha sido procesado.",
49 | "Could not connect with webhook service, verify URL": "No se ha podido conectar con el servicio de webhook.",
50 | "Service responded with an error on this moment. Please try again later": "El servicio respondió con errores."
51 | }
52 |
--------------------------------------------------------------------------------
/tests/BaseTestCase.php:
--------------------------------------------------------------------------------
1 | runMigrations(
35 | collect(
36 | [
37 | [
38 | 'class' => CreateUsersTable::class,
39 | 'file_path' => __DIR__.'/database/migrations/create_users_table.php',
40 | ],
41 | [
42 | 'class' => '\CreateCustomerColumns',
43 | 'file_path' => $migrations_dir.'/create_customer_columns.php.stub',
44 | ],
45 | [
46 | 'class' => '\CreateSubscriptionsTable',
47 | 'file_path' => $migrations_dir.'/create_subscriptions_table.php.stub',
48 | ],
49 | [
50 | 'class' => '\CreateCardsTable',
51 | 'file_path' => $migrations_dir.'/create_cards_table.php.stub',
52 | ],
53 | [
54 | 'class' => '\CreateBankAccountsTable',
55 | 'file_path' => $migrations_dir.'/create_bank_accounts_table.php.stub',
56 | ],
57 | ]
58 | )
59 | );
60 |
61 | return $this;
62 | }
63 |
64 | /**
65 | * Runs a collection of migrations.
66 | *
67 | * @param Collection $migrations
68 | */
69 | protected function runMigrations(Collection $migrations)
70 | {
71 | $migrations->each(function ($migration) {
72 | $this->runMigration($migration['class'], $migration['file_path']);
73 | });
74 | }
75 |
76 | /**
77 | * @param string $class
78 | * @param string $file_path
79 | */
80 | protected function runMigration($class, $file_path)
81 | {
82 | include_once $file_path;
83 | (new $class)->up();
84 | }
85 |
86 | protected function openpayExceptions()
87 | {
88 | include_once __DIR__.'/../vendor/openpay/sdk/data/OpenpayApiError.php';
89 | }
90 |
91 | /**
92 | * @param string $url
93 | * @param string $method
94 | * @param array $response
95 | * @return Request
96 | */
97 | protected function ajaxRequest($url = '/', $method = 'POST', $response = [])
98 | {
99 | $request = $this->request($url, $method, $response);
100 | $request->headers->set('X-Requested-With', 'XMLHttpRequest');
101 |
102 | return $request;
103 | }
104 |
105 | /**
106 | * @param string $url
107 | * @param string $method
108 | * @param array $response
109 | * @return Request
110 | */
111 | protected function request($url = '/', $method = 'POST', $response = [])
112 | {
113 | return Request::create(
114 | $url,
115 | $method,
116 | [],
117 | [],
118 | [],
119 | [],
120 | json_encode($response)
121 | );
122 | }
123 |
124 | /**
125 | * @return int
126 | */
127 | protected function randomExternalId()
128 | {
129 | return rand(1000, 10000);
130 | }
131 |
132 | /**
133 | * @param array $options
134 | * @return User
135 | */
136 | protected function createUser(array $options = [])
137 | {
138 | return User::create(array_merge([
139 | 'email' => 'email@cashier-test.com',
140 | 'name' => 'Taylor Otwell',
141 | 'password' => Hash::make('HelloCashier123'),
142 | ], $options));
143 | }
144 |
145 | /**
146 | * @param array $options
147 | * @return OpenpayPlan
148 | */
149 | protected function createPlan(array $options = [])
150 | {
151 | return OpenpayPlan::add(array_merge([
152 | 'amount' => 150.00,
153 | 'status_after_retry' => 'cancelled',
154 | 'retry_times' => 2,
155 | 'name' => 'Plan 1',
156 | 'repeat_unit' => 'month',
157 | 'trial_days' => '30',
158 | 'repeat_every' => '1',
159 | 'currency' => 'MXN',
160 | ], $options));
161 | }
162 |
163 | /**
164 | * @param array $options
165 | * @return array
166 | */
167 | protected function cardData(array $options = [])
168 | {
169 | return array_merge([
170 | 'holder_name' => 'Taylor Otwell',
171 | 'card_number' => '4111111111111111',
172 | 'cvv2' => '123',
173 | 'expiration_month' => '12',
174 | 'expiration_year' => '30',
175 | ], $options);
176 | }
177 |
178 | /**
179 | * @param array $options
180 | * @return array
181 | */
182 | protected function addressData(array $options = [])
183 | {
184 | return array_merge([
185 | 'line1' => 'Avenida Carranza 1115',
186 | 'postal_code' => '78230',
187 | 'state' => 'San Luis Potosí',
188 | 'city' => 'San Luis Potosí',
189 | 'country_code' => 'MX',
190 | ], $options);
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/src/Http/Controllers/WebhookController.php.stub:
--------------------------------------------------------------------------------
1 | active() || $this->onTrial() || $this->onGracePeriod();
39 | }
40 |
41 | /**
42 | * Determine if the subscription is active.
43 | *
44 | * @return bool
45 | */
46 | public function active()
47 | {
48 | return is_null($this->ends_at) || $this->onGracePeriod();
49 | }
50 |
51 | /**
52 | * Determine if the subscription is within its grace period after cancellation.
53 | *
54 | * @return bool
55 | */
56 | public function onGracePeriod()
57 | {
58 | return $this->ends_at && $this->ends_at->isFuture();
59 | }
60 |
61 | /**
62 | * Determine if the subscription is within its trial period.
63 | *
64 | * @return bool
65 | */
66 | public function onTrial()
67 | {
68 | return $this->trial_ends_at && $this->trial_ends_at->isFuture();
69 | }
70 |
71 | /**
72 | * Filter query by on trial.
73 | *
74 | * @param \Illuminate\Database\Eloquent\Builder $query
75 | * @return void
76 | */
77 | public function scopeOnTrial($query)
78 | {
79 | $query->whereNotNull('trial_ends_at')->where('trial_ends_at', '>', Carbon::now());
80 | }
81 |
82 | /**
83 | * Determine if the subscription is past due.
84 | *
85 | * @return bool
86 | */
87 | public function pastDue()
88 | {
89 | return $this->openpay_status === self::STATUS_PAST_DUE;
90 | }
91 |
92 | /**
93 | * Filter query by past due.
94 | *
95 | * @param \Illuminate\Database\Eloquent\Builder $query
96 | * @return void
97 | */
98 | public function scopePastDue($query)
99 | {
100 | $query->where('openpay_status', self::STATUS_PAST_DUE);
101 | }
102 |
103 | /**
104 | * Determine if the subscription is past due.
105 | *
106 | * @return bool
107 | */
108 | public function unpaid()
109 | {
110 | return $this->openpay_status === self::STATUS_UNPAID;
111 | }
112 |
113 | /**
114 | * Filter query by past due.
115 | *
116 | * @param \Illuminate\Database\Eloquent\Builder $query
117 | * @return void
118 | */
119 | public function scopeUnpaid($query)
120 | {
121 | $query->where('openpay_status', self::STATUS_UNPAID);
122 | }
123 |
124 | /**
125 | * Determine if the subscription is no longer active.
126 | *
127 | * @return bool
128 | */
129 | public function cancelled()
130 | {
131 | return ! is_null($this->ends_at);
132 | }
133 |
134 | /**
135 | * Filter query by cancelled.
136 | *
137 | * @param \Illuminate\Database\Eloquent\Builder $query
138 | * @return void
139 | */
140 | public function scopeCancelled($query)
141 | {
142 | $query->whereNotNull('ends_at');
143 | }
144 |
145 | /**
146 | * Filter query by not cancelled.
147 | *
148 | * @param \Illuminate\Database\Eloquent\Builder $query
149 | * @return void
150 | */
151 | public function scopeNotCancelled($query)
152 | {
153 | $query->whereNull('ends_at');
154 | }
155 |
156 | /**
157 | * Determine if the subscription has ended and the grace period has expired.
158 | *
159 | * @return bool
160 | */
161 | public function ended()
162 | {
163 | return $this->cancelled() && ! $this->onGracePeriod();
164 | }
165 |
166 | /**
167 | * Filter query by ended.
168 | *
169 | * @param \Illuminate\Database\Eloquent\Builder $query
170 | * @return void
171 | */
172 | public function scopeEnded($query)
173 | {
174 | $query->cancelled()->notOnGracePeriod();
175 | }
176 |
177 | /**
178 | * Get the user that owns the subscription.
179 | *
180 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
181 | */
182 | public function user()
183 | {
184 | return $this->owner();
185 | }
186 |
187 | /**
188 | * Get the model related to the subscription.
189 | *
190 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
191 | */
192 | public function owner()
193 | {
194 | $model = config('cashier_openpay.model');
195 |
196 | return $this->belongsTo($model, (new $model)->getForeignKey());
197 | }
198 |
199 | /**
200 | * Extend an existing subscription's trial period.
201 | *
202 | * @param CarbonInterface $date
203 | * @return Subscription
204 | * @throws \Exception
205 | */
206 | public function extendTrial(CarbonInterface $date)
207 | {
208 | if (! $date->isFuture()) {
209 | throw new \Exception("Extending a subscription's trial requires a date in the future.");
210 | }
211 |
212 | $subscription = $this->asOpenpaySubscription();
213 |
214 | $subscription->trial_end_date = $date->getTimestamp();
215 |
216 | $subscription->save();
217 |
218 | $this->trial_ends_at = $date;
219 |
220 | $this->save();
221 |
222 | return $this;
223 | }
224 |
225 | /**
226 | * Cancel the subscription at the end of the billing period.
227 | *
228 | * @return $this
229 | */
230 | public function cancel()
231 | {
232 | $subscription = $this->asOpenpaySubscription();
233 |
234 | $subscription->cancel_at_period_end = true;
235 |
236 | $subscription = $subscription->save();
237 |
238 | $this->openpay_status = self::STATUS_CANCELLED;
239 |
240 | if ($this->onTrial()) {
241 | $this->ends_at = $this->trial_ends_at;
242 | } else {
243 | $this->ends_at = $subscription->charge_date;
244 | }
245 |
246 | $this->save();
247 |
248 | return $this;
249 | }
250 |
251 | /**
252 | * Cancel the subscription immediately.
253 | *
254 | * @return $this
255 | */
256 | public function cancelNow()
257 | {
258 | $this->asOpenpaySubscription()->delete();
259 |
260 | $this->fill([
261 | 'openpay_status' => self::STATUS_CANCELLED,
262 | 'ends_at' => Carbon::now(),
263 | ])->save();
264 |
265 | return $this;
266 | }
267 |
268 | /**
269 | * Determine if the subscription has a specific plan.
270 | *
271 | * @param int $plan
272 | * @return bool
273 | */
274 | public function hasPlan($plan)
275 | {
276 | return $this->openpay_plan == $plan;
277 | }
278 |
279 | /**
280 | * Get the subscription as a Openpay subscription object.
281 | *
282 | * @return \OpenpaySubscription
283 | */
284 | public function asOpenpaySubscription()
285 | {
286 | $customer = $this->owner->asOpenpayCustomer();
287 |
288 | return OpenpaySubscription::find($this->openpay_id, $customer);
289 | }
290 | }
291 |
--------------------------------------------------------------------------------
/src/OpenpayExceptionsHandler.php:
--------------------------------------------------------------------------------
1 | getMessage();
34 | $http_error_code = $exception->getHttpCode();
35 | $error_code = $exception->getErrorCode();
36 |
37 | if ($error_code >= 1000 && $error_code <= 1020) {
38 | $message = $this->generalErrors($error_code);
39 | } elseif ($error_code >= 2001 && $error_code <= 2003) {
40 | $message = $this->storageErrors($error_code);
41 | } elseif ($error_code >= 2004 && $error_code <= 3205) {
42 | $message = $this->cardsErrors($error_code);
43 | } elseif ($error_code >= 4001 && $error_code <= 4002) {
44 | $message = $this->accountsErrors($error_code);
45 | } elseif ($error_code >= 6001 && $error_code <= 6003) {
46 | $message = $this->webhooksErrors($error_code);
47 | }
48 |
49 | $response_data = [
50 | 'openpay_error_request_id' => $exception->getRequestId(),
51 | 'openpay_error_message' => $message,
52 | 'openpay_error_message_original' => $exception->getMessage(),
53 | 'openpay_error_http_code' => $http_error_code,
54 | 'openpay_error_category' => $exception->getCategory(),
55 | 'openpay_error_code' => $error_code,
56 | ];
57 |
58 | if (config('cashier_openpay.log_errors')) {
59 | Log::error('OPENPAY ERROR REQUEST ID: '.$exception->getRequestId());
60 | Log::error('OPENPAY ERROR MESSAGE: '.$exception->getMessage());
61 | Log::error('OPENPAY ERROR HTTP CODE: '.$http_error_code);
62 | Log::error('OPENPAY ERROR CODE: '.$error_code);
63 | Log::error('OPENPAY ERROR CATEGORY: '.$exception->getCategory());
64 | }
65 |
66 | if ($request->ajax() || $request->wantsJson()) {
67 | return response()->json($response_data, $http_error_code);
68 | } else {
69 | return back()
70 | ->withInput()
71 | ->withErrors($response_data, 'cashier');
72 | }
73 | }
74 |
75 | protected function generalErrors($error_code)
76 | {
77 | switch ($error_code) {
78 | case 1000:
79 | return __('Internal server error, contact support');
80 | case 1001:
81 | return __('Bad Request');
82 | case 1002:
83 | return __('The api key or merchant id are invalid');
84 | case 1003:
85 | return __('Parameters look valid but request failed');
86 | case 1004:
87 | return __('The resource is unavailable at this moment. Please try again later');
88 | case 1005:
89 | return __('The requested resource doesn\'t exist');
90 | case 1006:
91 | return __('The order_id has already been processed');
92 | case 1007:
93 | return __('Operation rejected by processor');
94 | case 1008:
95 | return __('The account is inactive');
96 | case 1009:
97 | return __('The request is too large');
98 | case 1010:
99 | return __('Method not allowed for public API key, use private key instead');
100 | case 1011:
101 | return __('The resource was previously deleted');
102 | case 1012:
103 | return __('The transaction amount exceeds your allowed transaction limit');
104 | case 1013:
105 | return __('The operation is not allowed on the resource');
106 | case 1014:
107 | return __('Your account is inactive, please contact to soporte@openpay.mx for more information');
108 | case 1015:
109 | return __('Could not get any response from gateway. Please try again later');
110 | case 1016:
111 | return __('The merchant email has been already processed');
112 | case 1017:
113 | return __('The payment gateway is not available at the moment, please try again later');
114 | case 1018:
115 | return __('The number of retries of charge is greater than allowed');
116 | case 1020:
117 | return __('The number of decimal digits is not valid for this currency');
118 | }
119 | }
120 |
121 | protected function storageErrors($error_code)
122 | {
123 | switch ($error_code) {
124 | case 2001:
125 | return __('The bank account already exists');
126 | case 2003:
127 | return __('The external_id already exists');
128 | }
129 | }
130 |
131 | protected function cardsErrors($error_code)
132 | {
133 | switch ($error_code) {
134 | case 2004:
135 | return __('The card number verification digit is invalid');
136 | case 2005:
137 | return __('The expiration date has expired');
138 | case 2006:
139 | return __('The CVV2 security code is required');
140 | case 2007:
141 | return __('The card number is only valid in sandbox');
142 | case 2008:
143 | return __('The card is not valid for points');
144 | case 2009:
145 | return __('The CVV2 security code is invalid');
146 | case 2010:
147 | return __('3D Secure authentication failed');
148 | case 2011:
149 | return __('Card product type not supported');
150 | case 3001:
151 | return __('The card was declined by the bank');
152 | case 3002:
153 | return __('The card has expired');
154 | case 3003:
155 | return __('The card doesn\'t have sufficient funds');
156 | case 3004:
157 | return __('The card was reported as stolen');
158 | case 3005:
159 | return __('Fraud risk detected by anti-fraud system');
160 | case 3006:
161 | return __('Request not allowed');
162 | case 3009:
163 | return __('The card was reported as lost');
164 | case 3010:
165 | return __('The bank has restricted the card');
166 | case 3011:
167 | return __('The bank has requested the card to be retained');
168 | case 3012:
169 | return __('Bank authorization is required for this charge');
170 | case 3201:
171 | return __('Merchant not authorized to use payment plan');
172 | case 3203:
173 | return __('Invalid promotion for such card type');
174 | case 3204:
175 | return __('Transaction amount is less than minimum for promotion');
176 | case 3205:
177 | return __('Promotion not allowed');
178 | }
179 | }
180 |
181 | protected function accountsErrors($error_code)
182 | {
183 | switch ($error_code) {
184 | case 4001:
185 | return __('There are not enough funds in the openpay account');
186 | case 4002:
187 | return __('The operation can\'t be completed until pending fees are paid');
188 | }
189 | }
190 |
191 | protected function webhooksErrors($error_code)
192 | {
193 | switch ($error_code) {
194 | case 6001:
195 | return __('The webhook has already been processed');
196 | case 6002:
197 | return __('Could not connect with webhook service, verify URL');
198 | case 6003:
199 | return __('Service responded with an error on this moment. Please try again later');
200 | }
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/src/Billable.php:
--------------------------------------------------------------------------------
1 | $amount,
24 | ], $options);
25 |
26 | $customer = $this->asOpenpayCustomer();
27 |
28 | return OpenpayCharge::create($options, $customer);
29 | }
30 |
31 | /**
32 | * @param $charge_id
33 | * @param null $amount
34 | * @param string $description
35 | * @return mixed
36 | */
37 | public function refund($charge_id, $description = '', $amount = null)
38 | {
39 | $refund_data = [
40 | 'description' => $description,
41 | ];
42 |
43 | if ($amount != null) {
44 | $refund_data = array_merge([
45 | 'amount' => $amount,
46 | ], $refund_data);
47 | }
48 |
49 | $customer = $this->asOpenpayCustomer();
50 |
51 | return OpenpayCharge::refund($charge_id, $refund_data, $customer);
52 | }
53 |
54 | /**
55 | * @param $plan_id
56 | * @param string $name
57 | * @param array $options
58 | * @return Subscription
59 | */
60 | public function newSubscription($plan_id, array $options = [], $name = 'default')
61 | {
62 | $options = array_merge([
63 | 'plan_id' => $plan_id,
64 | ], $options);
65 |
66 | $customer = $this->asOpenpayCustomer();
67 | $openpay_subscription = OpenpaySubscription::add($options, $customer);
68 |
69 | /** @var Subscription $subscription */
70 | $subscription = $this->subscriptions()->create([
71 | 'name' => $name,
72 | 'user_id' => $this->id,
73 | 'openpay_id' => $openpay_subscription->id,
74 | 'openpay_status' => $openpay_subscription->status,
75 | 'openpay_plan' => $plan_id,
76 | 'trial_ends_at' => $openpay_subscription->trial_end_date,
77 | 'ends_at' => null,
78 | ]);
79 |
80 | $subscription->save();
81 |
82 | return $subscription;
83 | }
84 |
85 | /**
86 | * @param string $name
87 | * @param null $plan
88 | * @return bool
89 | */
90 | public function subscribed($name = 'default', $plan = null)
91 | {
92 | $subscription = $this->subscription($name);
93 |
94 | if (! $subscription || ! $subscription->valid()) {
95 | return false;
96 | }
97 |
98 | return $plan ? $subscription->hasPlan($plan) : true;
99 | }
100 |
101 | /**
102 | * @param $plans
103 | * @param string $name
104 | * @return bool
105 | */
106 | public function subscribedToPlan($plans, $name = 'default')
107 | {
108 | $subscription = $this->subscription($name);
109 |
110 | if (! $subscription || ! $subscription->valid()) {
111 | return false;
112 | }
113 |
114 | foreach ((array) $plans as $plan) {
115 | if ($subscription->hasPlan($plan)) {
116 | return true;
117 | }
118 | }
119 |
120 | return false;
121 | }
122 |
123 | /**
124 | * @param $plan
125 | * @return bool
126 | */
127 | public function onPlan($plan)
128 | {
129 | return ! is_null($this->subscriptions->first(function (Subscription $subscription) use ($plan) {
130 | return $subscription->valid() && $subscription->hasPlan($plan);
131 | }));
132 | }
133 |
134 | /**
135 | * Determine if the model is on trial.
136 | *
137 | * @param string $name
138 | * @param string|null $plan
139 | * @return bool
140 | */
141 | public function onTrial($name = 'default', $plan = null)
142 | {
143 | $subscription = $this->subscription($name);
144 |
145 | if (! $subscription || ! $subscription->onTrial()) {
146 | return false;
147 | }
148 |
149 | return $plan ? $subscription->hasPlan($plan) : true;
150 | }
151 |
152 | /**
153 | * @param string $name
154 | * @return Subscription|null
155 | */
156 | public function subscription($name = 'default')
157 | {
158 | return $this->subscriptions()->where('name', $name)->first();
159 | }
160 |
161 | /**
162 | * Get all of the subscriptions for the Openpay model.
163 | *
164 | * @return HasMany
165 | */
166 | public function subscriptions()
167 | {
168 | return $this->hasMany(Subscription::class, $this->getForeignKey());
169 | }
170 |
171 | /**
172 | * @param $card_data
173 | * @param $address
174 | * @param array $options
175 | * @return Card
176 | */
177 | public function addCard(array $card_data, array $address = [], array $options = [])
178 | {
179 | if ($address != []) {
180 | $card_data['address'] = $address;
181 | }
182 |
183 | $data = array_merge($card_data, $options);
184 |
185 | $customer = $this->asOpenpayCustomer();
186 | $card_openpay = OpenpayCard::add($data, $customer);
187 |
188 | /** @var Card $card */
189 | $card = $this->cards()->create([
190 | 'user_id' => $this->id,
191 | 'openpay_id' => $card_openpay->id,
192 | 'type' => $card_openpay->type,
193 | 'brand' => $card_openpay->brand,
194 | 'holder_name' => $card_openpay->holder_name,
195 | 'card_number' => $card_openpay->card_number,
196 | 'expiration_month' => $card_openpay->expiration_month,
197 | 'expiration_year' => $card_openpay->expiration_year,
198 | 'bank_name' => $card_openpay->bank_name,
199 | 'bank_code' => $card_openpay->bank_code,
200 | ]);
201 |
202 | $card->save();
203 |
204 | return $card;
205 | }
206 |
207 | /**
208 | * @return HasMany
209 | */
210 | public function cards()
211 | {
212 | return $this->hasMany(Card::class, $this->getForeignKey());
213 | }
214 |
215 | public function addBankAccount(array $bank_account_data)
216 | {
217 | $customer = $this->asOpenpayCustomer();
218 | $bank_account = OpenpayBankAccount::add($bank_account_data, $customer);
219 |
220 | /** @var BankAccount $bank_account */
221 | $bank_account = $this->bank_accounts()->create([
222 | 'user_id' => $this->id,
223 | 'openpay_id' => $bank_account->id,
224 | 'holder_name' => $bank_account->holder_name,
225 | 'clabe' => $bank_account->clabe,
226 | 'bank_name' => $bank_account->bank_name,
227 | 'bank_code' => $bank_account->bank_code,
228 | 'alias' => $bank_account->alias,
229 | ]);
230 |
231 | $bank_account->save();
232 |
233 | return $bank_account;
234 | }
235 |
236 | /**
237 | * @return HasMany
238 | */
239 | public function bank_accounts()
240 | {
241 | return $this->hasMany(BankAccount::class, $this->getForeignKey());
242 | }
243 |
244 | /**
245 | * Determine if the entity has a OpenPay customer ID.
246 | *
247 | * @return bool
248 | */
249 | public function hasOpenpayId()
250 | {
251 | return ! is_null($this->openpay_id);
252 | }
253 |
254 | /**
255 | * Create a Openpay customer for the given Openpay model.
256 | *
257 | * @param array $options
258 | * @return OpenpayCustomer
259 | */
260 | public function createAsOpenpayCustomer(array $options = [])
261 | {
262 | if ($this->hasOpenpayId()) {
263 | return $this->asOpenpayCustomer();
264 | }
265 |
266 | $options = array_key_exists('name', $options) ? $options : array_merge($options, ['name' => $this->name]);
267 | $options = array_key_exists('email', $options) ? $options : array_merge($options, ['email' => $this->email]);
268 | $options = array_key_exists('external_id', $options) ? $options : array_merge($options, ['external_id' => $this->id]);
269 |
270 | $customer = Customer::add($options);
271 |
272 | $this->openpay_id = $customer->id;
273 | $this->save();
274 |
275 | return $customer;
276 | }
277 |
278 | /**
279 | * Get the Openpay customer for the Openpay model.
280 | *
281 | * @return OpenpayCustomer
282 | */
283 | public function asOpenpayCustomer()
284 | {
285 | if (is_null($this->openpay_id)) {
286 | return null;
287 | }
288 |
289 | return Customer::find($this->openpay_id);
290 | }
291 |
292 | /**
293 | * Get the Openpay customer for the Openpay model.
294 | *
295 | * @return OpenpayCustomer
296 | */
297 | public function createOrGetOpenpayCustomer()
298 | {
299 | if ($this->openpay_id) {
300 | return Customer::find($this->openpay_id);
301 | }
302 |
303 | $this->createAsOpenpayCustomer();
304 | }
305 | }
306 |
--------------------------------------------------------------------------------
/tests/Traits/OpenpayExceptionHandlerTest.php:
--------------------------------------------------------------------------------
1 | openpayExceptions();
23 | }
24 |
25 | public function testIsOpenPayExceptionGenericException()
26 | {
27 | $result = (new Handler)->isOpenpayException(new Exception);
28 |
29 | $this->assertFalse($result);
30 | }
31 |
32 | public function testIsOpenPayExceptionApiError()
33 | {
34 | $result = (new Handler)->isOpenpayException(new OpenpayApiError);
35 |
36 | $this->assertTrue($result);
37 | }
38 |
39 | public function testIsOpenPayExceptionApiAuthError()
40 | {
41 | $result = (new Handler)->isOpenpayException(new OpenpayApiAuthError);
42 |
43 | $this->assertTrue($result);
44 | }
45 |
46 | public function testIsOpenPayExceptionApiTransactionError()
47 | {
48 | $result = (new Handler)->isOpenpayException(new OpenpayApiTransactionError);
49 |
50 | $this->assertTrue($result);
51 | }
52 |
53 | public function testIsOpenPayExceptionApiConnectionError()
54 | {
55 | $result = (new Handler)->isOpenpayException(new OpenpayApiConnectionError);
56 |
57 | $this->assertTrue($result);
58 | }
59 |
60 | public function testRenderWithGenericException()
61 | {
62 | $request = $this->request();
63 | $message = 'Error';
64 |
65 | try {
66 | throw new Exception($message);
67 | } catch (Exception $exception) {
68 | $response = (new Handler)->render($request, $exception);
69 | }
70 |
71 | $this->assertEquals(json_encode(['message' => $message]), $response->getContent());
72 | }
73 |
74 | public function testRenderJsonAjaxRequestWithApiAuthError()
75 | {
76 | $request = $this->ajaxRequest();
77 |
78 | $request_id = 1;
79 | $exception_message = 'Error';
80 | $message = __('Internal server error, contact support');
81 | $category = 'request';
82 | $error_code = 1000;
83 | $http_code = 404;
84 | $fraud_rules = [];
85 |
86 | $expected_response_data = $this->expectedResponseJson($message, $exception_message, $error_code, $category, $request_id, $http_code, $fraud_rules);
87 |
88 | try {
89 | throw new OpenpayApiAuthError($exception_message, $error_code, $category, $request_id, $http_code, $fraud_rules);
90 | } catch (Exception $exception) {
91 | $response = (new Handler)->render($request, $exception);
92 | }
93 |
94 | $this->assertEquals($expected_response_data, $response->getContent());
95 | $this->assertEquals($http_code, $response->getStatusCode());
96 | }
97 |
98 | public function testRenderJsonAjaxRequestWithApiTransactionError()
99 | {
100 | $request = $this->ajaxRequest();
101 |
102 | $request_id = 1;
103 | $exception_message = 'The card number verification digit is invalid';
104 | $message = __('The card number verification digit is invalid');
105 | $category = 'request';
106 | $error_code = 2004;
107 | $http_code = 422;
108 | $fraud_rules = [];
109 |
110 | $expected_response_data = $this->expectedResponseJson($message, $exception_message, $error_code, $category, $request_id, $http_code, $fraud_rules);
111 |
112 | try {
113 | throw new OpenpayApiTransactionError($exception_message, $error_code, $category, $request_id, $http_code, $fraud_rules);
114 | } catch (Exception $exception) {
115 | $response = (new Handler)->render($request, $exception);
116 | }
117 |
118 | $this->assertEquals($expected_response_data, $response->getContent());
119 | $this->assertEquals($http_code, $response->getStatusCode());
120 | }
121 |
122 | public function testRenderJsonAjaxRequestWithApiRequestError()
123 | {
124 | $request = $this->ajaxRequest();
125 |
126 | $request_id = 1;
127 | $exception_message = 'The card number verification digit is invalid';
128 | $message = __('The card number verification digit is invalid');
129 | $category = 'request';
130 | $error_code = 2004;
131 | $http_code = 422;
132 | $fraud_rules = [];
133 |
134 | $expected_response_data = $this->expectedResponseJson($message, $exception_message, $error_code, $category, $request_id, $http_code, $fraud_rules);
135 |
136 | try {
137 | throw new OpenpayApiRequestError($exception_message, $error_code, $category, $request_id, $http_code, $fraud_rules);
138 | } catch (Exception $exception) {
139 | $response = (new Handler)->render($request, $exception);
140 | }
141 |
142 | $this->assertEquals($expected_response_data, $response->getContent());
143 |
144 | $this->assertEquals($http_code, $response->getStatusCode());
145 | }
146 |
147 | public function testRenderBackWithApiTransactionError()
148 | {
149 | $request = $this->request();
150 | $request_id = 1;
151 | $exception_message = 'Error';
152 | $message = __('The card number verification digit is invalid');
153 | $category = 'request';
154 | $error_code = 2004;
155 | $http_code = 422;
156 | $fraud_rules = [];
157 |
158 | $expected_response_data = $this->expectedResponseHtml();
159 |
160 | try {
161 | throw new OpenpayApiTransactionError($exception_message, $error_code, $category, $request_id, $http_code, $fraud_rules);
162 | } catch (Exception $exception) {
163 | $response = (new Handler)->render($request, $exception);
164 | }
165 |
166 | $this->assertEquals($expected_response_data, $response->getContent());
167 | $this->assertEquals([$request_id], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_request_id'));
168 | $this->assertEquals([$message], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_message'));
169 | $this->assertEquals([$exception_message], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_message_original'));
170 | $this->assertEquals([$http_code], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_http_code'));
171 | $this->assertEquals([$category], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_category'));
172 | $this->assertEquals([$error_code], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_code'));
173 | $this->assertEquals(302, $response->getStatusCode());
174 | }
175 |
176 | public function testRenderBackWithUnknownOpenpayError()
177 | {
178 | $request = $this->request();
179 | $request_id = 1;
180 | $exception_message = 'Error';
181 | $message = 'Error';
182 | $category = 'request';
183 | $error_code = 8000;
184 | $http_code = 500;
185 | $fraud_rules = [];
186 |
187 | $expected_response_data = $this->expectedResponseHtml();
188 |
189 | try {
190 | throw new OpenpayApiTransactionError($exception_message, $error_code, $category, $request_id, $http_code, $fraud_rules);
191 | } catch (Exception $exception) {
192 | $response = (new Handler)->render($request, $exception);
193 | }
194 |
195 | $this->assertEquals($expected_response_data, $response->getContent());
196 | $this->assertEquals([$request_id], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_request_id'));
197 | $this->assertEquals([$message], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_message'));
198 | $this->assertEquals([$exception_message], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_message_original'));
199 | $this->assertEquals([$http_code], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_http_code'));
200 | $this->assertEquals([$category], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_category'));
201 | $this->assertEquals([$error_code], $response->getSession()->get('errors')->getbag('cashier')->get('openpay_error_code'));
202 | $this->assertEquals(302, $response->getStatusCode());
203 | }
204 |
205 | private function expectedResponseJson($message, $original_message, $code, $category, $request_id, $http_code, $fraud_rules)
206 | {
207 | return json_encode([
208 | 'openpay_error_request_id' => $request_id,
209 | 'openpay_error_message' => $message,
210 | 'openpay_error_message_original' => $original_message,
211 | 'openpay_error_http_code' => $http_code,
212 | 'openpay_error_category' => $category,
213 | 'openpay_error_code' => $code,
214 | ]);
215 | }
216 |
217 | private function expectedResponseHtml()
218 | {
219 | $expected_response_data = file_get_contents(__DIR__.'/../Fixtures/redirect_localhost.html');
220 |
221 | return substr($expected_response_data, 0, -1);
222 | }
223 | }
224 |
225 | class Handler
226 | {
227 | use OpenpayExceptionsHandler;
228 |
229 | /**
230 | * @param $request
231 | * @param Throwable $exception
232 | * @return JsonResponse|RedirectResponse
233 | */
234 | public function render($request, Throwable $exception)
235 | {
236 | if ($this->isOpenpayException($exception)) {
237 | return $this->renderOpenpayException($request, $exception);
238 | }
239 |
240 | return response()->json(['message' => $exception->getMessage()]);
241 | }
242 | }
243 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # CashierOpenpay
2 |
3 | [![Latest Version on Packagist][ico-version]][link-packagist]
4 | [![Total Downloads][ico-downloads]][link-downloads]
5 | [![Build Status][ico-travis]][link-travis]
6 | 
7 |
8 | 
9 |
10 | ## Installation
11 |
12 | Require the Cashier package for Openpay with Composer:
13 |
14 | ```bash
15 | composer require perafan/cashier-openpay
16 | ```
17 |
18 | | CashierOpenpay | Laravel |
19 | | :------------: |:--------:|
20 | | 1.X | 7.X |
21 | | 2.X | 8.X |
22 |
23 | Run to publish migrations, WebHookController and config file.
24 |
25 | ```bash
26 | php artisan vendor:publish --tag="cashier-openpay-migrations"
27 | php artisan vendor:publish --tag="cashier-openpay-configs"
28 | php artisan vendor:publish --tag="cashier-openpay-webhook-controller"
29 | ```
30 |
31 | The Cashier service provider registers its own database migration directory, so remember to migrate your database after installing the package.
32 | The Cashier migrations will add several columns to your users table as well as create a new subscriptions table to hold all of your customer's subscriptions:
33 |
34 | ``` bash
35 | php artisan migrate
36 | ```
37 |
38 | ## Configuration
39 |
40 | ### Billable Model
41 |
42 | Add the `Billable` trait to your model definition.
43 | `Billable` trait provides methods to allow yo to perform common billing tasks (creating subscriptions, add payment method information, creating charges ,etc.)
44 |
45 | ```php
46 | use Perafan\CashierOpenpay\Billable;
47 |
48 | class User extends Authenticatable
49 | {
50 | use Billable;
51 | }
52 | ```
53 |
54 | Cashier assumes your Billable model will be the `App\Models\User class that ships with Laravel. If you wish to change this you can specify a different model in your `.env` file:
55 |
56 | ```dotenv
57 | OPENPAY_MODEL=App\Models\User
58 | ```
59 |
60 | ### API Keys
61 | Next, you should configure your Openpay keys in your .env file. You can retrieve your Openpay API keys from the Openpay control panel.
62 |
63 | ```dotenv
64 | OPENPAY_PUBLIC_KEY=-your-openpay-public-key-
65 | OPENPAY_PRIVATE_KEY=-your-openpay-private-key-
66 | OPENPAY_ID=-your-openpay-id-
67 | ```
68 |
69 | ### Environment
70 |
71 | By convenience and security, the sandbox mode is activated by default in the client library. This allows you to test your own code when implementing Openpay, before charging any credit card in production environment.
72 |
73 | ```dotenv
74 | OPENPAY_PRODUCTION_MODE=false
75 | ```
76 |
77 | ### Logging
78 |
79 | Cashier allows you to specify the log channel to be used when logging all Openpay related exceptions.
80 |
81 | ```dotenv
82 | OPENPAY_LOG_ERRORS=true
83 | ```
84 |
85 | ### Show openpay errors (Optional)`
86 |
87 | If you want to catch all the openpay exceptions add in your `app/Exceptions/Handler.php`
88 |
89 | ```php
90 | isOpenpayException($exception)) {
106 | return $this->renderOpenpayException($request, $exception);
107 | }
108 | return parent::render($request, $exception);
109 | }
110 | }
111 | ```
112 |
113 | To render the error response in blade you could use the follow snippets.
114 |
115 | #### Show errors with [bootstrap](https://getbootstrap.com/)
116 |
117 | ```blade
118 | @if($errors->cashier->isNotEmpty())
119 |
120 | @foreach ($errors->cashier->keys() as $key)
121 | {{ $key }} : {{ $errors->cashier->get($key)[0] }}
122 | @endforeach
123 |
124 | @endif
125 | ```
126 |
127 | #### Show errors with [tailwindcss](https://tailwindcss.com/)
128 |
129 | ```blade
130 | @if($errors->cashier->isNotEmpty())
131 |
132 | @foreach ($errors->cashier->keys() as $key)
133 | {{ $key }} : {{ $errors->cashier->get($key)[0] }}
134 | @endforeach
135 |
136 | @endif
137 | ```
138 |
139 | #### Your own Openpay Exceptions Handler (Optional)
140 |
141 | You can modify the response creating your own handler.
142 |
143 | ```php
144 | trait MyOpenpayExceptionsHandler
145 | {
146 | use OpenpayExceptionsHandler {
147 | OpenpayExceptionsHandler::renderOpenpayException as parentRenderOpenpayException;
148 | }
149 |
150 | public function renderOpenpayException(Request $request, OpenpayApiError $exception)
151 | {
152 | $this->parentRenderOpenpayException($request, $exception);
153 |
154 | //your code
155 | }
156 | }
157 | ```
158 |
159 | ## Use
160 |
161 | ### Customers
162 |
163 |
164 | **On a User:**
165 |
166 | Add a new customer to a merchant:
167 |
168 | ```php
169 | $user->createAsOpenpayCustomer();
170 |
171 | //Or you can send additional data
172 |
173 | $data = [
174 | 'name' => 'Teofilo',
175 | 'last_name' => 'Velazco',
176 | 'phone_number' => '4421112233',
177 | 'address' => [
178 | 'line1' => 'Privada Rio No. 12',
179 | 'line2' => 'Co. El Tintero',
180 | 'line3' => '',
181 | 'postal_code' => '76920',
182 | 'state' => 'Querétaro',
183 | 'city' => 'Querétaro.',
184 | 'country_code' => 'MX'
185 | ]
186 | ];
187 |
188 | $openpay_customer = $user->createAsOpenpayCustomer($data);
189 | ````
190 |
191 | Get a customer:
192 | ```php
193 | $openpay_customer = $user->asOpenpayCustomer();
194 | ```
195 |
196 | Update a customer:
197 | ```php
198 | $openpay_customer = $user->asOpenpayCustomer();
199 | $openpay_customer->name = 'Juan';
200 | $openpay_customer->last_name = 'Godinez';
201 | $openpay_customer->save();
202 | ```
203 |
204 | Delete a customer:
205 | ```php
206 | $openpay_customer = $user->asOpenpayCustomer();
207 | $openpay_customer->delete();
208 | ```
209 |
210 | **On a merchant:**
211 |
212 | Add a new customer to a merchant:
213 |
214 | ```php
215 | use Perafan\CashierOpenpay\Openpay\Customer as OpenpayCustomer;
216 |
217 | OpenpayCustomer::create([
218 | 'name' => 'Teofilo',
219 | 'last_name' => 'Velazco',
220 | 'email' => 'teofilo@payments.com',
221 | 'phone_number' => '4421112233',
222 | 'address' => [
223 | 'line1' => 'Privada Rio No. 12',
224 | 'line2' => 'Co. El Tintero',
225 | 'line3' => '',
226 | 'postal_code' => '76920',
227 | 'state' => 'Querétaro',
228 | 'city' => 'Querétaro.',
229 | 'country_code' => 'MX'
230 | ]
231 | ]);
232 | ````
233 |
234 | Get a customer:
235 | ```php
236 | use Perafan\CashierOpenpay\Openpay\Customer as OpenpayCustomer;
237 |
238 | $customer = OpenpayCustomer::find('a9ualumwnrcxkl42l6mh');
239 | ```
240 |
241 | Get the list of customers:
242 | ```php
243 | use Perafan\CashierOpenpay\Openpay\Customer as OpenpayCustomer;
244 |
245 | $customer_list = OpenpayCustomer::all();
246 |
247 | // with filters
248 |
249 | $filters = [
250 | 'creation[gte]' => '2020-01-01',
251 | 'creation[lte]' => '2020-12-31',
252 | 'offset' => 0,
253 | 'limit' => 5
254 | ];
255 |
256 | $customer_list = OpenpayCustomer::all($filters);
257 | ```
258 |
259 | Update a customer:
260 | ```php
261 | use Perafan\CashierOpenpay\Openpay\Customer as OpenpayCustomer;
262 |
263 | $customer = OpenpayCustomer::find('a9ualumwnrcxkl42l6mh');
264 | $customer->name = 'Juan';
265 | $customer->last_name = 'Godinez';
266 | $customer->save();
267 | ```
268 |
269 | Delete a customer:
270 | ```php
271 | use Perafan\CashierOpenpay\Openpay\Customer as OpenpayCustomer;
272 |
273 | $customer = OpenpayCustomer::find('a9ualumwnrcxkl42l6mh');
274 | $customer->delete();
275 | ```
276 |
277 | #### Cards ####
278 |
279 | **On a user:**
280 |
281 | Add a card:
282 | ```php
283 | $card_data = [
284 | 'holder_name' => 'Teofilo Velazco',
285 | 'card_number' => '4916394462033681',
286 | 'cvv2' => '123',
287 | 'expiration_month' => '12',
288 | 'expiration_year' => '15'
289 | ];
290 |
291 | $card = $user->addCard($card_data);
292 |
293 | // with token
294 |
295 | $card_data = [
296 | 'token_id' => 'tokgslwpdcrkhlgxqi9a',
297 | 'device_session_id' => '8VIoXj0hN5dswYHQ9X1mVCiB72M7FY9o'
298 | ];
299 |
300 | $card = $user->addCard($card_data);
301 |
302 | // with address
303 |
304 | $address_data = [
305 | 'line1' => 'Privada Rio No. 12',
306 | 'line2' => 'Co. El Tintero',
307 | 'line3' => '',
308 | 'postal_code' => '76920',
309 | 'state' => 'Querétaro',
310 | 'city' => 'Querétaro.',
311 | 'country_code' => 'MX'
312 | ];
313 |
314 | $card = $user->addCard($card_data, $address_data);
315 | ```
316 |
317 | Get a card:
318 | ```php
319 | use Perafan\CashierOpenpay\Card;
320 |
321 | $card = $user->cards->first;
322 | //or
323 | $card = Card::find(1);
324 |
325 | $openpay_card = $card->asOpenpayCard();
326 | ```
327 |
328 | Get user cards:
329 | ```php
330 | use Perafan\CashierOpenpay\Card;
331 |
332 | $cards = $user->cards;
333 | // or
334 | $cards = Card::where('user_id', $user->id)->get();
335 | ```
336 |
337 | Get user cards from Openpay
338 | ```php
339 | use Perafan\CashierOpenpay\Card;
340 | use Perafan\CashierOpenpay\Openpay\Card as OpenpayCard;
341 |
342 | $cards = $user->cards;
343 |
344 | $openpay_cards = $cards->map(function($card) {
345 | return $card->asOpenpayCard();
346 | });
347 |
348 | // or
349 |
350 | $cards = Card::where('user_id', $user->id)->get();
351 |
352 | $openpay_cards = $cards->map(function($card) {
353 | return $card->asOpenpayCard();
354 | });
355 |
356 | // or
357 |
358 | $openpay_customer = $user->asOpenpayCustomer();
359 |
360 | $openpay_cards = OpenpayCard::all([], $openpay_customer);
361 |
362 | // with filters
363 |
364 | $filters = [
365 | 'creation[gte]' => '2020-01-01',
366 | 'creation[lte]' => '2020-12-31',
367 | 'offset' => 0,
368 | 'limit' => 5
369 | ];
370 |
371 | $openpay_cards = OpenpayCard::all($filters, $openpay_customer);
372 | ```
373 |
374 | Delete a card
375 | ```php
376 | use Perafan\CashierOpenpay\Card;
377 |
378 | $card = $user->cards->first;
379 | //or
380 | $card = Card::find(1);
381 |
382 | $openpay_card = $card->asOpenpayCard();
383 | $deleted_card = $openpay_card->delete();
384 |
385 | if (!is_array($deleted_card)) {
386 | //The card was deleted in Openpay
387 | $card->delete();
388 | }
389 | ```
390 |
391 | **On a merchant:**
392 |
393 | Add a card:
394 | ```php
395 | use Perafan\CashierOpenpay\Openpay\Card as OpenpayCard;
396 |
397 | $card_data = [
398 | 'holder_name' => 'Teofilo Velazco',
399 | 'card_number' => '4916394462033681',
400 | 'cvv2' => '123',
401 | 'expiration_month' => '12',
402 | 'expiration_year' => '15'
403 | ];
404 |
405 | $openpay_card = OpenpayCard::add($card_data);
406 |
407 | // with token
408 |
409 | $card_data = [
410 | 'token_id' => 'tokgslwpdcrkhlgxqi9a',
411 | 'device_session_id' => '8VIoXj0hN5dswYHQ9X1mVCiB72M7FY9o'
412 | ];
413 |
414 | $openpay_card = OpenpayCard::add($card_data);
415 |
416 | // with address
417 |
418 | $address_data = [
419 | 'line1' => 'Privada Rio No. 12',
420 | 'line2' => 'Co. El Tintero',
421 | 'line3' => '',
422 | 'postal_code' => '76920',
423 | 'state' => 'Querétaro',
424 | 'city' => 'Querétaro.',
425 | 'country_code' => 'MX'
426 | ];
427 |
428 | $card_data['address'] = $address_data;
429 |
430 | $openpay_card = OpenpayCard::add($card_data);
431 | ```
432 |
433 | Get a card:
434 | ```php
435 | use Perafan\CashierOpenpay\Openpay\Card as OpenpayCard;
436 |
437 | $openpay_card = OpenpayCard::find('k9pn8qtsvr7k7gxoq1r5');
438 | ```
439 |
440 | Get the list of cards:
441 | ```php
442 | use Perafan\CashierOpenpay\Openpay\Card as OpenpayCard;
443 |
444 | $openpay_card = OpenpayCard::all();
445 |
446 | // with filters
447 |
448 | $filters = [
449 | 'creation[gte]' => '2020-01-01',
450 | 'creation[lte]' => '2020-12-31',
451 | 'offset' => 0,
452 | 'limit' => 5
453 | ];
454 |
455 | $openpay_card = OpenpayCard::all($filters);
456 | ```
457 |
458 | Delete a card:
459 | ```php
460 | use Perafan\CashierOpenpay\Openpay\Card as OpenpayCard;
461 |
462 | $openpay_card = OpenpayCard::find('k9pn8qtsvr7k7gxoq1r5');
463 | $openpay_card->delete();
464 | //Card was not delete on your database, only was deleted in openpay
465 | ```
466 |
467 | #### Bank Accounts ####
468 |
469 | Add a bank account to a customer:
470 | ```php
471 | $bank_data = [
472 | 'clabe' => '072910007380090615',
473 | 'alias' => 'Cuenta principal',
474 | 'holder_name' => 'Teofilo Velazco'
475 | ];
476 |
477 | $bank_account = $user->addBankAccount($bank_data);
478 | ```
479 |
480 | Get a bank account
481 | ```php
482 | use Perafan\CashierOpenpay\BankAccount;
483 |
484 | $bank_account = $user->bank_accounts->first;
485 | // or
486 | $bank_account = BankAccount::where('user_id', $user->id)->first();
487 |
488 | $openpay_bank_account = $bank_account->asOpenpayBankAccount();
489 | ```
490 |
491 | Get user bank accounts:
492 | ```php
493 | use Perafan\CashierOpenpay\BankAccount;
494 |
495 | $bank_accounts = $user->bank_accounts;
496 | // or
497 | $bank_accounts = BankAccount::where('user_id', $user->id)->get();
498 | ```
499 |
500 | Get user bank accounts from Openpay:
501 | ```php
502 | use Perafan\CashierOpenpay\BankAccount;
503 | use Perafan\CashierOpenpay\Openpay\BankAccount as OpenpayBankAccount;
504 |
505 | $bank_accounts = $user->bank_accounts;
506 |
507 | $openpay_bank_accounts = $bank_accounts->map(function($bank_account) {
508 | return $bank_account->asOpenpayBankAccount();
509 | });
510 |
511 | // or
512 |
513 | $bank_accounts = BankAccount::where('user_id', $user->id)->get();
514 |
515 | $openpay_bank_accounts = $bank_accounts->map(function($bank_account) {
516 | return $bank_account->asOpenpayBankAccount();
517 | });
518 |
519 | // or
520 |
521 | $openpay_customer = $user->asOpenpayCustomer();
522 |
523 | $openpay_bank_accounts = OpenpayBankAccount::all([], $openpay_customer);
524 |
525 | // with filters
526 |
527 | $filters = [
528 | 'creation[gte]' => '2020-01-01',
529 | 'creation[lte]' => '2020-12-31',
530 | 'offset' => 0,
531 | 'limit' => 5
532 | ];
533 |
534 | $openpay_bank_accounts = OpenpayBankAccount::all($filters, $openpay_customer);
535 | ```
536 |
537 | Delete a bank account:
538 | ```php
539 | use Perafan\CashierOpenpay\BankAccount;
540 |
541 | $bank_account = $user->bank_accounts->first;
542 | // or
543 | $bank_account = BankAccount::where('user_id', $user->id)->first();
544 |
545 | $openpay_bank_account = $bank_account->asOpenpayBankAccount();
546 |
547 | $deleted_bank_account = $openpay_bank_account->delete();
548 |
549 | if (!is_array($deleted_bank_account)) {
550 | //The card was deleted in Openpay
551 | $bank_account->delete();
552 | }
553 | ```
554 |
555 |
556 | #### Charges ####
557 |
558 | **On a Customer:**
559 |
560 | Make a charge on a customer:
561 | ```php
562 | $charge_data = [
563 | 'source_id' => 'tvyfwyfooqsmfnaprsuk',
564 | 'method' => 'card',
565 | 'currency' => 'MXN',
566 | 'description' => 'Cargo inicial a mi merchant',
567 | 'order_id' => 'oid-00051',
568 | 'device_session_id' => 'kR1MiQhz2otdIuUlQkbEyitIqVMiI16f',
569 | ];
570 |
571 | $openpay_charge = $user->charge(100, $charge_data);
572 | ```
573 |
574 | Get a charge:
575 | ```php
576 | use Perafan\CashierOpenpay\Openpay\Charge as OpenpayCharge;
577 |
578 | $openpay_charge = OpenpayCharge::find('a9ualumwnrcxkl42l6mh');
579 | ```
580 |
581 | Get list of charges per user:
582 | ```php
583 | use Perafan\CashierOpenpay\Openpay\Charge as OpenpayCharge;
584 |
585 | $filters = [
586 | 'creation[gte]' => '2020-01-01',
587 | 'creation[lte]' => '2020-12-31',
588 | 'offset' => 0,
589 | 'limit' => 5
590 | ];
591 |
592 | $openpay_customer = $user->asOpenpayCustomer();
593 |
594 | $openpay_charge = OpenpayCharge::all($filters, $openpay_customer);
595 | ```
596 |
597 | Make a capture:
598 | ```php
599 | use Perafan\CashierOpenpay\Openpay\Charge as OpenpayCharge;
600 |
601 | $capture_data = ['amount' => 150.00];
602 |
603 | $openpay_charge = OpenpayCharge::find('a9ualumwnrcxkl42l6mh');
604 |
605 | $openpay_charge->capture($capture_data);
606 | ```
607 |
608 | Make a refund:
609 | ```php
610 | // Send charge id as first param
611 | $charge_id = 'tvyfwyfooqsmfnaprsuk';
612 | $user->refund($charge_id);
613 | //or
614 |
615 | $description = 'Reembolso';
616 | $user->refund($charge_id, $description);
617 |
618 | //or
619 | $amount = 150.00;
620 | $user->refund($charge_id, $description, $amount);
621 | ```
622 |
623 | **On a Merchant:**
624 |
625 | Make a charge on a merchant:
626 | ```php
627 | use Perafan\CashierOpenpay\Openpay\Charge as OpenpayCharge;
628 |
629 | $charge_data = [
630 | 'method' => 'card',
631 | 'source_id' => 'krfkkmbvdk3hewatruem',
632 | 'amount' => 100,
633 | 'description' => 'Cargo inicial a mi merchant',
634 | 'order_id' => 'ORDEN-00071'
635 | ];
636 |
637 | $openpay_charge = OpenpayCharge::create($charge_data);
638 | ```
639 |
640 | Get a charge:
641 | ```php
642 | use Perafan\CashierOpenpay\Openpay\Charge as OpenpayCharge;
643 |
644 | $openpay_charge = OpenpayCharge::find('tvyfwyfooqsmfnaprsuk');
645 | ```
646 |
647 | Get list of charges:
648 | ```php
649 | use Perafan\CashierOpenpay\Openpay\Charge as OpenpayCharge;
650 |
651 | $openpay_charges = OpenpayCharge::all();
652 |
653 | // with filters
654 | $filters = [
655 | 'creation[gte]' => '2020-01-01',
656 | 'creation[lte]' => '2020-12-31',
657 | 'offset' => 0,
658 | 'limit' => 5
659 | ];
660 |
661 | $openpay_charges = OpenpayCharge::all($filters);
662 | ```
663 |
664 | Make a capture:
665 | ```php
666 | use Perafan\CashierOpenpay\Openpay\Charge as OpenpayCharge;
667 |
668 | $capture_data = ['amount' => 150.00];
669 |
670 | $openpay_charge = OpenpayCharge::find('tvyfwyfooqsmfnaprsuk');
671 | $capture_data->capture($capture_data);
672 | ```
673 |
674 | Make a refund:
675 | ```php
676 | use Perafan\CashierOpenpay\Openpay\Charge as OpenpayCharge;
677 |
678 | $refund_data = ['description' => 'Devolución'];
679 |
680 | $openpay_charge = OpenpayCharge::find('tvyfwyfooqsmfnaprsuk');
681 | $openpay_charge->refund($refund_data);
682 | ```
683 |
684 | #### Transfers ####
685 |
686 | Make a transfer:
687 | ```php
688 | $transfer_data = [
689 | 'customer_id' => 'aqedin0owpu0kexr2eor',
690 | 'amount' => 12.50,
691 | 'description' => 'Cobro de Comisión',
692 | 'order_id' => 'ORDEN-00061'
693 | ];
694 |
695 | $openpay_customer = $user->asOpenpayCustomer();
696 | $transfer = $openpay_customer->transfers->create($transfer_data);
697 | ```
698 |
699 | Get a transfer:
700 | ```php
701 | $openpay_customer = $user->asOpenpayCustomer();
702 | $transfer = $openpay_customer->transfers->get('tyxesptjtx1bodfdjmlb');
703 | ```
704 |
705 | Get list of transfers:
706 | ```php
707 | $filters = [
708 | 'creation[gte]' => '2020-01-01',
709 | 'creation[lte]' => '2020-12-31',
710 | 'offset' => 0,
711 | 'limit' => 5
712 | ];
713 |
714 | $openpay_customer = $user->asOpenpayCustomer();
715 | $transfer_list = $openpay_customer->transfers->getList($filters);
716 | ```
717 |
718 | #### Payouts ####
719 |
720 | **On a Customer:**
721 |
722 | Make a payout on a customer:
723 | ```php
724 | $payout_data = [
725 | 'method' => 'card',
726 | 'destination_id' => 'k9pn8qtsvr7k7gxoq1r5',
727 | 'amount' => 1000,
728 | 'description' => 'Retiro de saldo semanal',
729 | 'order_id' => 'ORDEN-00062'
730 | ];
731 |
732 | $openpay_customer = $user->asOpenpayCustomer();
733 | $payout = $openpay_customer->payouts->create($payout_data);
734 | ```
735 |
736 | Get a payout:
737 | ```php
738 | $openpay_customer = $user->asOpenpayCustomer();
739 | $payout = $openpay_customer->payouts->get('tysznlyigrkwnks6eq2c');
740 | ```
741 |
742 | Get list pf payouts:
743 | ```php
744 | $filters = [
745 | 'creation[gte]' => '2020-01-01',
746 | 'creation[lte]' => '2020-12-31',
747 | 'offset' => 0,
748 | 'limit' => 5
749 | ];
750 |
751 | $openpay_customer = $user->asOpenpayCustomer();
752 | $payout_list = $customer->payouts->getList($filters);
753 | ```
754 |
755 | #### Fees ####
756 | Pending ...
757 |
758 | #### Plans ####
759 |
760 | Add a plan:
761 | ```php
762 | use Perafan\CashierOpenpay\Openpay\Plan as OpenpayPlan;
763 |
764 | $plan_data = [
765 | 'amount' => 150.00,
766 | 'status_after_retry' => 'cancelled',
767 | 'retry_times' => 2,
768 | 'name' => 'Plan Curso Verano',
769 | 'repeat_unit' => 'month',
770 | 'trial_days' => '30',
771 | 'repeat_every' => '1',
772 | 'currency' => 'MXN'
773 | ];
774 |
775 | $openpay_plan = OpenpayPlan::add($plan_data);
776 | ```
777 |
778 | Get a plan:
779 | ```php
780 | use Perafan\CashierOpenpay\Openpay\Plan as OpenpayPlan;
781 |
782 | $openpay_plan = OpenpayPlan::find('pduar9iitv4enjftuwyl');
783 | ```
784 |
785 | Get list of plans:
786 | ```php
787 | use Perafan\CashierOpenpay\Openpay\Plan as OpenpayPlan;
788 |
789 | $openpay_plans = OpenpayPlan::all();
790 | // with filters
791 | $filters = [
792 | 'creation[gte]' => '2020-01-01',
793 | 'creation[lte]' => '2020-12-31',
794 | 'offset' => 0,
795 | 'limit' => 5
796 | ];
797 |
798 | $openpay_plans = OpenpayPlan::all($filters);
799 | ```
800 |
801 | Update a plan:
802 | ```php
803 | use Perafan\CashierOpenpay\Openpay\Plan as OpenpayPlan;
804 |
805 | $openpay_plan = OpenpayPlan::find('pduar9iitv4enjftuwyl');
806 | $openpay_plan->name = 'Plan Curso de Verano 2021';
807 | $openpay_plan->save();
808 | ```
809 |
810 | Delete a plan:
811 | ```php
812 | use Perafan\CashierOpenpay\Openpay\Plan as OpenpayPlan;
813 |
814 | $openpay_plan = OpenpayPlan::find('pduar9iitv4enjftuwyl');
815 | $openpay_plan->delete();
816 | ```
817 |
818 | Get list of subscriptors of a plan:
819 | ```php
820 | use Perafan\CashierOpenpay\Openpay\Plan as OpenpayPlan;
821 |
822 | $openpay_plan = OpenpayPlan::find('pduar9iitv4enjftuwyl');
823 |
824 | $filters = [
825 | 'creation[gte]' => '2020-01-01',
826 | 'creation[lte]' => '2020-12-31',
827 | 'offset' => 0,
828 | 'limit' => 5
829 | ];
830 |
831 | $subscription_list = $openpay_plan->subscriptions->getList($filters);
832 | ```
833 |
834 | #### Subscriptions ####
835 |
836 | Add a subscription:
837 | ```php
838 | $plan_id = 'pduar9iitv4enjftuwyl';
839 |
840 | $subscription = $user->newSubscription($plan_id);
841 |
842 | // Add the name of subscription
843 | $options = [
844 | 'trial_end_date' => '2021-01-01',
845 | 'card_id' => 'konvkvcd5ih8ta65umie'
846 | ];
847 |
848 | $subscription = $user->newSubscription($plan_id, $options);
849 |
850 | // Add the name of subscription
851 | $name = 'plan_verano_2021';
852 |
853 | $subscription = $user->newSubscription($plan_id, $options, $name);
854 | ```
855 |
856 | Checking Subscription Status
857 | ```php
858 | $name = 'plan_verano_2021';
859 | $user->subscribed($name);
860 |
861 | $name_2027 = 'plan_verano_2027';
862 | $user->subscribed($name_2027);
863 |
864 | $plan_id = 'pduar9iitv4enjftuwyl';
865 | $user->subscribed($name, $plan_id);
866 |
867 | $user->subscribed($name, 'ptyui9iit40nfwftuwyl');
868 | ```
869 |
870 | ```php
871 | $plans = [
872 | 'pduar9iitv4enjftuwyl',
873 | 'ptyui9iit40nfwftuwyl'
874 | ];
875 |
876 | $user->subscribedToPlan($plans);
877 | ```
878 |
879 | Subscription Trial
880 | ```php
881 | $subscription = $user->subscription();
882 |
883 | $subscription->onTrial();
884 | ```
885 |
886 |
887 | Checking User Trial
888 | ```php
889 | $subscription_name = 'plan_verano_2027';
890 |
891 | $user->onTrial($subscription_name);
892 | ```
893 |
894 | Get a subscription:
895 | ```php
896 | use Perafan\CashierOpenpay\Subscription;
897 | $subscription = $user->subscriptions->first;
898 | // or
899 | $subscription = Subscription::find('s7ri24srbldoqqlfo4vp');
900 |
901 | $subscription->asOpenpaySubscription();
902 | ```
903 |
904 | Get list of subscriptions:
905 | ```php
906 | $openpay = Openpay::getInstance('moiep6umtcnanql3jrxp', 'sk_3433941e467c1055b178ce26348b0fac');
907 |
908 | $filters = [
909 | 'creation[gte]' => '2020-01-01',
910 | 'creation[lte]' => '2020-12-31',
911 | 'offset' => 0,
912 | 'limit' => 5
913 | ];
914 |
915 | $customer = $openpay->customers->get('a9ualumwnrcxkl42l6mh');
916 | $subscriptionList = $customer->subscriptions->getList($filters);
917 | ```
918 |
919 | Update a subscription:
920 | ```php
921 | $openpay = Openpay::getInstance('moiep6umtcnanql3jrxp', 'sk_3433941e467c1055b178ce26348b0fac');
922 |
923 | $customer = $openpay->customers->get('a9ualumwnrcxkl42l6mh');
924 | $subscription = $customer->subscriptions->get('s7ri24srbldoqqlfo4vp');
925 | $subscription->trial_end_date = '2021-12-31';
926 | $subscription->save();
927 | ```
928 |
929 | Delete a subscription:
930 | ```php
931 | $openpay = Openpay::getInstance('moiep6umtcnanql3jrxp', 'sk_3433941e467c1055b178ce26348b0fac');
932 |
933 | $customer = $openpay->customers->get('a9ualumwnrcxkl42l6mh');
934 | $subscription = $customer->subscriptions->get('s7ri24srbldoqqlfo4vp');
935 | $subscription->delete();
936 | ```
937 |
938 | ## Openpay SDK
939 |
940 | Many of Cashier's objects are wrappers around Openpay SDK objects. If you would like to interact with the Openpay objects directly, you may conveniently retrieve them using the `asOpenpay...` methods:
941 |
942 | ```php
943 |
944 | $openpayCustomer = $user->asOpenpayCustomer();
945 |
946 | $openpayCustomer->name = 'Pedro';
947 |
948 | $openpayCustomer->save();
949 |
950 | $openpaySubscription = $subscription->asOpenpaySubscription();
951 |
952 | $subscription->trial_end_date = '2021-12-31';
953 |
954 | $openpaySubscription->save();
955 | ```
956 |
957 | ## Testing
958 |
959 | To get started, add the testing version of your Openpay keys to your phpunit.xml file:
960 |
961 | ```xml
962 |
963 |
964 |
965 | ```
966 |
967 | Then you can run on your term
968 |
969 | ``` bash
970 | $ composer install
971 | $ vendor/bin/phpunit
972 | ```
973 |
974 | ## Contributing
975 |
976 | Please see [contributing.md](contributing.md) for details and a todolist.
977 |
978 | ## Security
979 |
980 | If you discover any security related issues, please email pedro.perafan.carrasco@gmail.com instead of using the issue tracker.
981 |
982 | ## Credits
983 |
984 | - [Pedro Perafán Carrasco][link-author]
985 |
986 | ## License
987 |
988 | MIT. Please see the [license file](license.md) for more information.
989 |
990 | [ico-version]: https://img.shields.io/packagist/v/perafan/cashier-openpay.svg?style=flat-square
991 | [ico-downloads]: https://img.shields.io/packagist/dt/perafan/cashier-openpay.svg?style=flat-square
992 | [ico-travis]: https://img.shields.io/travis/Perafan18/cashier-openpay/master.svg?style=flat-square
993 | [ico-styleci]: https://styleci.io/repos/133202140/shield
994 |
995 | [link-packagist]: https://packagist.org/packages/perafan/cashier-openpay
996 | [link-downloads]: https://packagist.org/packages/perafan/cashier-openpay
997 | [link-travis]: https://travis-ci.org/github/Perafan18/cashier-openpay
998 | [link-styleci]: https://styleci.io/repos/133202140
999 | [link-author]: https://github.com/perafan18
1000 |
1001 |
1002 | [https://www.openpay.mx/docs/api/#devolver-un-cargo]: https://www.openpay.mx/docs/api/#devolver-un-cargo
--------------------------------------------------------------------------------