PHP

ec-cube4 タグ 検索 – ECCUBE4の商品検索で商品タグも条件に追加するANDじゃなくてORで – Customizer(Where| Query)だと出来ない?

EC-Cube4で商品検索でタグも検索条件に追加したいときどうすれば出来るのかいろいろ試してみました。

そもそも商品検索のクエリにdtb_product_tagが含まれていないので、WhereCustomizerではなくQueryCustomizerで下記のように作りました。

namespace Customize\Repository;

use Doctrine\ORM\QueryBuilder;
use Eccube\Doctrine\Query\QueryCustomizer;
use Eccube\Repository\QueryKey;

class TagSearchQueryCustomizer implements QueryCustomizer {

    protected $tag;

    public function __construct(TagRepository $tagRepository){
        $this->tag = $tagRepository;
    }

    public function getQueryKey() {
        return QueryKey::PRODUCT_SEARCH;
    }

    public function customize(QueryBuilder $builder, $params, $queryKey){
        if ($params['name']) {

            $builder->innerJoin("p.ProductTag", "pt");

            $tags = $this->tag->createQueryBuilder('t')
                    ->where("NORMALIZE( t.name ) LIKE NORMALIZE( :name )")
                    ->setParameter("name", '%' . $params['name'] . '%')
                    ->getQuery()->getResult();

            foreach ($tags as $tag) {
                $builder->orWhere("pt.Tag = :TagId");
                $builder->setParameter("TagId", $tag->getId());
            }
        }
    }
}

タグレポジトリからLIKE検索した結果のタグIDをキーに検索してくれるのですが、これだと下記のようなクエリになってしまい、表示属性や廃止属性を無視してしまいます。

FROM
    dtb_product d0_
    INNER JOIN
        dtb_product_class d1_
    ON  d0_.id = d1_.product_id
    AND d1_.discriminator_type IN('productclass')
    INNER JOIN
        dtb_product_tag d2_
    ON  d0_.id = d2_.product_id
    AND d2_.discriminator_type IN('producttag')
WHERE
    (
        (
            d0_.product_status_id = 1
        AND (
                CONVERT(d0_.name USING utf8) COLLATE utf8_unicode_ci LIKE CONVERT(? USING utf8) COLLATE utf8_unicode_ci
            OR  CONVERT(d0_.search_word USING utf8) COLLATE utf8_unicode_ci LIKE CONVERT(? USING utf8) COLLATE utf8_unicode_ci
            OR  EXISTS(
                    SELECT
                        d3_.id
                    FROM
                        dtb_product_class d3_
                    WHERE
                        (
                            d0_.id = d3_.product_id
                        AND CONVERT(d3_.product_code USING utf8) COLLATE utf8_unicode_ci LIKE CONVERT(? USING utf8) COLLATE utf8_unicode_ci
                        )
                    AND d3_.discriminator_type IN('productclass')
                )
            )
        AND d1_.visible = 1
        )
    OR  d2_.tag_id = ?
    )
AND d0_.discriminator_type IN('product')

search_wordやnameをみているグルーピングの中にORでタグ条件を入れたいのですが、QueryCustomizerだと入れ込めませんでした。。

次にWhereCustomizerならsearch_wordやnameの部分に入れ込めるか試してみました。

<?php
namespace Customize\Repository;

use Eccube\Doctrine\Query\WhereClause;
use Eccube\Doctrine\Query\WhereCustomizer;
use Eccube\Repository\QueryKey;
use Eccube\Repository\TagRepository;

class TagSearchWhereCustomizer extends WhereCustomizer {

    protected $tag;

    public function __construct(TagRepository $tagRepository) {
        $this->tag = $tagRepository;
    }

    public function getQueryKey() {
        return QueryKey::PRODUCT_SEARCH;
    }

    protected function createStatements($params, $queryKey) {

        $whereCluse = [];

        if ($params['name']) {

            $tags = $this->tag->createQueryBuilder('t')
                ->where("NORMALIZE( t.name ) LIKE NORMALIZE( :name )")
                ->setParameter("name", '%' . $params['name'] . '%')
                ->getQuery()->getResult();

            // foreach ($tags as $tag) {
            //  // $whereCluse[] = WhereClause::eq("pt.Tag", ':Tag', $tag->getId());
            // }
        }

        return [
            WhereClause::isNull('p.note'),
        ];

        // return $whereCluse;
    }

}

一旦テストとして、dtb_productのノート属性を検索条件にしてcreateStatementsの戻り値がクエリのどこに追加されるのか確認してみました。

 INNER JOIN
        dtb_product_tag d2_
    ON  d0_.id = d2_.product_id
    AND d2_.discriminator_type IN('producttag')
WHERE
    (
        d0_.product_status_id = 1
    AND (
            CONVERT(d0_.name USING utf8) COLLATE utf8_unicode_ci LIKE CONVERT(? USING utf8) COLLATE utf8_unicode_ci
        OR  CONVERT(d0_.search_word USING utf8) COLLATE utf8_unicode_ci LIKE CONVERT(? USING utf8) COLLATE utf8_unicode_ci
        OR  EXISTS(
                SELECT
                    d3_.id
                FROM
                    dtb_product_class d3_
                WHERE
                    (
                        d0_.id = d3_.product_id
                    AND CONVERT(d3_.product_code USING utf8) COLLATE utf8_unicode_ci LIKE CONVERT(? USING utf8) COLLATE utf8_unicode_ci
                    )
                AND d3_.discriminator_type IN('productclass')
            )
        )
    AND d1_.visible = 1
    AND d0_.note IS NULL

WhereCustomizerでも思った部分にはクエリは追加されませんでした。QueryCustomizerでinnerJoinして、WhereCustomizerで条件追加という作戦は失敗のようです。

ここに来てやっとProductRepositoryのソースをみてみると、getQueryBuilderBySearchDataの中でandWhereで追加したいグループが追加された後、customizeが呼ばれているので、自分が入れ込みたい場所にいれるにはProductRepositoryをいじる必要があるのかもしれない。。

    public function getQueryBuilderBySearchData($searchData)
    {
        $qb = $this->createQueryBuilder('p')
            ->andWhere('p.Status = 1');

        // category
        $categoryJoin = false;
        if (!empty($searchData['category_id']) && $searchData['category_id']) {
            $Categories = $searchData['category_id']->getSelfAndDescendants();
            if ($Categories) {
                $qb
                    ->innerJoin('p.ProductCategories', 'pct')
                    ->innerJoin('pct.Category', 'c')
                    ->andWhere($qb->expr()->in('pct.Category', ':Categories'))
                    ->setParameter('Categories', $Categories);
                $categoryJoin = true;
            }
        }

        // name
        if (isset($searchData['name']) && StringUtil::isNotBlank($searchData['name'])) {
            $keywords = preg_split('/[\s ]+/u', str_replace(['%', '_'], ['\\%', '\\_'], $searchData['name']), -1, PREG_SPLIT_NO_EMPTY);

            foreach ($keywords as $index => $keyword) {
                $key = sprintf('keyword%s', $index);
                $qb
                    ->andWhere(sprintf('NORMALIZE(p.name) LIKE NORMALIZE(:%s) OR 
                        NORMALIZE(p.search_word) LIKE NORMALIZE(:%s) OR 
                        EXISTS (SELECT wpc%d FROM \Eccube\Entity\ProductClass wpc%d WHERE p = wpc%d.Product AND NORMALIZE(wpc%d.code) LIKE NORMALIZE(:%s))',
                        $key, $key, $index, $index, $index, $index, $key))
                    ->setParameter($key, '%'.$keyword.'%');
            }
        }

並び替えなど。。。

 return $this->queries->customize(QueryKey::PRODUCT_SEARCH, $qb, $searchData);
    }

ECCUBEの作法というかいろいろわかってないけど、ProductRepository直接編集ってNGな気がするけど、どうするのが正しいんだろうか。。

もしProductRepositoryを修正していいならこんな感じでできたけど、proxyとかでやるのかな?

foreach ($keywords as $index => $keyword) {
                $key = sprintf('keyword%s', $index);
                $qb
                    ->andWhere(sprintf('NORMALIZE(p.name) LIKE NORMALIZE(:%s) OR 
                        NORMALIZE(p.search_word) LIKE NORMALIZE(:%s) OR 
                        EXISTS (SELECT wpc%d FROM \Eccube\Entity\ProductClass wpc%d WHERE p = wpc%d.Product AND NORMALIZE(wpc%d.code) LIKE NORMALIZE(:%s)) OR
                        EXISTS (
                            SELECT eept%d FROM \Eccube\Entity\ProductTag eept%d
                            WHERE p = eept%d.Product
                            AND eept%d.Tag IN (
                                SELECT eet%d FROM \Eccube\Entity\Tag eet%d
                                WHERE NORMALIZE( eet%d.name ) LIKE NORMALIZE( :%s )
                            )
                        )',
						$key, $key, $index, $index, $index, $index, $key, 
						$index, $index, $index, $index, $index, $index, $index, $key))
                    ->setParameter($key, '%'.$keyword.'%');

traitの例はカラム追加とかが多くて、メソッド書き換の方法を調べてみます。

Twilio-PHP-SDK 4.xのdialを5.xにアップデートでDOMExceptionエラー(response->dialを5.xに対応させる)

TwilioPHP-SDKの4.xで書かれたこんなカンファレンスへの誘導と、完了後にsayするconference_complet.phpへのdial発信

着信のwebhookで起動します。

include './twilio-php/Services/Twilio.php';
include './twilio-php/Services/Twilio/Capability.php';

$response = new Services_Twilio_Twiml();
$dial = $response->dial(array(
	'action' => "https://***.com/conference_complet.php",
 	"method" => "GET",
));
$dial->conference("My Conference", array(
	"startConferenceOnEnter" => "true",
	"waitUrl" => "https://***.com/conference_wait.php",
 	"endConferenceOnExit" => "true",
 	"beep" => "true",
));
print $response;

dialの部分を下記のようにTwilio-PHP-SDK ver5.xで書いたのですが、print responseでXMLのDOMエラーが発生しました。

PHP Fatal error:  Method Twilio\\TwiML\\VoiceResponse::__toString() must not throw an exception, caught DOMException: Invalid Character Error 

conferenceは動くので、dialに問題があるようです。

$response = new VoiceResponse();

$dial = $response->dial([
	'action' => "https://***.com/conference_complet.php",
 	"method" => "GET",
));
print $response;

4.xで出力されるTwiMLは下記のようになっており、Dial要素の中はConferenceなので、dialにはactionなどのパラメータしか入力していないのですが、5.xではエラーになります

<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Dial action="https://***.com/conference_complet.php" method="GET">
<Conference startConferenceOnEnter="true" waitUrl="https://***.com/conference_wait.php" endConferenceOnExit="true" beep="true">My Conference</Conference>
</Dial>
</Response>

下記のようにdialの引数を変更すると、動作します。

$response = new VoiceResponse();

$dial = $response->dial("",[
	'action' => "https://***.com/conference_complet.php",
 	"method" => "GET",
));
print $response;

<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Dial action="https://***.com/conference_complet.php" method="GET">
<Conference startConferenceOnEnter="true" waitUrl="https://***.com/conference_wait.php" endConferenceOnExit="true" beep="true">My Conference</Conference>
</Dial>
</Response>

PHP Eloquentで既存のPDOを利用する – 既存プロジェクトにEloquent追加

既存の独自フレームワークに新しい機能を追加する機会があり、DB周りを確認したのですが、PDOをゴリゴリに使っており、サニタイズも独自だったのでせめて追加の部分はライブラリ(Eloquent)を利用して安全にしたいと思いましたが、追加分の為にフィルター系の処理をEloquentで別で作って管理するのは別の題もあるので、PDOは残しつつ、Eloquentを利用しようとおもいます。

が、 PDOと別でEloquentでコネクションを作るのはトラブルの元なので、既存処理で接続状態になっているPDOを利用します。

いつもだと

$capsule = new Capsule;
$capsule->addConnection([
	'driver' => 'mysql',
	'host' => 'db.local',
	'database' => 'test',
	'username' => 'db_test',
	'password' => 'pass0000',
	'charset' => 'utf8',
	'collation' => 'utf8_unicode_ci',
	'prefix' => '',
]);
$capsule->setAsGlobal();
$capsule->bootEloquent();

上記の様にEloquent単体で利用する場合は、Capsuleを初期化してますが今回は既存のPDOがあるので、下記の様にしてMySqlConnectionを生成してEloquentのModelに渡します。

$existPDO = 既存のPDOオブジェクトを取得();
$mySqlCon = new MySqlConnection($existPDO);
$resolver = new ConnectionResolver([$mySqlCon]);
$resolver->setDefaultConnection(0);
Model::setConnectionResolver($resolver);

上記でSELECTは動作したのですが、Eloquent Modelでsaveが動きませんでした。EloquentのQueryLogを確認しても問題ないクエリですし、エラーも出ません。save()の戻りもtrue。。

試しにEloquentのモデル側でconnectionをdefault指定したのですが、queryエラーが出たので、どうやらsetDefaultConnection(0)では更新時には’default’として働かない?ようなので、下記に変更しました。


$mySqlCon = new MySqlConnection($existPDO);
$resolver = new ConnectionResolver();
$resolver->addConnection('default', $mySqlCon);
$resolver->setDefaultConnection('default');
Model::setConnectionResolver($resolver);

Twilio+ngrok Twilio番号に着信があった場合のWebhookのREQUEST (A CALL COMES IN)

Twilioを利用する必要が出たので、ドキュメントを見ながらAPIなどを確認中

いろいろ機能はあるけど、まずは基本の部分を見てみる。
LINEボットなどと同様にTwilioのconsoleから着信時にHTTPのWebhookを設定できるので、サーバーにおいてもいいけど、ngrokが楽だよとドキュメントにもあるので、利用してみる。

ngrokにアカウント登録して、実行するとngrok.ioのサブドメインへのアクセスを実行してるローカルマシンの8000にフォワードしてくれる。

ngrok by @inconshreveable                                       (Ctrl+C to quit)

Session Status                online
Account                       droid (Plan: Free)
Version                       2.3.35
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://704be30b.ngrok.io -> http://localhost:8000
Forwarding                    https://704be30b.ngrok.io -> http://localhost:8000

なので、上記でいう「http://704be30b.ngrok.io」を公開URLとして、
Twilioのコンソールで
http://704be30b.ngrok.io/callback.php
を「Programmable Voice」の「受電したい番号」から「A CALL COMES IN」のWebhookURLを設定

?php
// パラメータが見たいだけなので、TwilioSDKは今回不要
error_log(print_r($_REQUEST, true));

上記に加えて受信スクリプトを配置した場所で実行して、Twilioの番号に電話をかける

RC211v:app dp$ php -S localhost:8000
PHP 7.3.5 Development Server started at Fri Jan 24 12:53:18 2020
Listening on http://localhost:8000
Document root is /Users/dp/projects/twilio/app
Press Ctrl-C to quit.
[Fri Jan 24 12:54:13 2020] [::1]:62430 [200]: /callback.php
[Fri Jan 24 12:56:26 2020] Array
(
    [Called] => +125647****Twilioで取得した受電番号
    [ToState] => AL
    [CallerCountry] => JP
    [Direction] => inbound
    [CallerState] =>
    [ToZip] => 35739
    [CallSid] => CA7f52ed3ec************************
    [To] => +125647****Twilioで取得した受電番号
    [CallerZip] =>
    [ToCountry] => US
    [ApiVersion] => 2010-04-01
    [CalledZip] => 35739
    [CalledCity] => ELKMONT
    [CallStatus] => ringing
    [From] => +81801234発信者番号
    [AccountSid] => AC31a5******************************
    [CalledCountry] => US
    [CallerCity] =>
    [Caller] => +81801234発信者番号
    [FromCountry] => JP
    [ToCity] => ELKMONT
    [FromCity] =>
    [CalledState] => AL
    [FromZip] =>
    [FromState] =>
)

カンファレンスなどでFromを判定したりするには、$_REQUESTのformなどを元にすればいいということか。

–>

PHPでBOMを削除 ( remove \xEF\xBB\xBF | U+FEFF | efbbbf)

LINE messaging APIでTextファイルを受信した際に、BOMがついており先頭の空白削除がうまく動作しなかったので、PHPでUTF8のBOMを削除する方法がいくつかあったのでメモ

preg_replaceで削除するパターン
今回はこっちを使って削除

return preg_replace("/^\xEF\xBB\xBF/", '', $text);

U+FEFFで指定するパターン

$str = preg_replace('/\x{FEFF}/u', '', $file);

hexで削除するパターン

function remove_utf8_bom_head($text) {
    if(substr(bin2hex($text), 0, 6) === 'efbbbf') {
        $text = substr($text, 3);
    }
    return $text;
}

PHPでBOM削除で検索すると、自分が前に公開していたJSONから削除する記事が上位に表示され恥ずかしい。

https://stackoverflow.com/questions/10290849/how-to-remove-multiple-utf-8-bom-sequences

SlimフレームワークでLaravelで有名なEloquentのEvent(self::creating,updating,savingイベント)が動かなかったのを解決できた

EloquentはLaravelでなくても便利なのでORMとしてCodeigniterやSlimで利用してますが
Eloquentのモデルが設定したイベントが呼ばれないので調べてみました。

エラーにはならず、普通にORMとして機能するんですけど、イベントが如何せんよばれない。。


class User extends Model {
public static function boot(){
	parent::boot();
	self::creating(function ($q) {
log出力とかしたい。
	});
	self::updating(function ($q) {
log出力とかしたいのに、イベントが呼ばれない。。
	});
}

$user = User::find(1);
$user->last_login_at = time();
$user->save();

最初slim eloquent event not working,slim eloquent event not callなどで
ググったけど、SlimでEloquentとなるとModelの基本しか見つからないので
vendorの中をちゃんと読むことにしました。

Illuminate\Database\Eloquent\Model.phpを見ると
トレイトにHasEventsってぽいやつがいる。
そして中でイベントが設定されていれば、Dispatcher読んでる。



Model.php

namespace Illuminate\Database\Eloquent;

abstract class Model implements ArrayAccess, Arrayable, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable
{
    use Concerns\HasAttributes,
        Concerns\HasEvents,
        Concerns\HasGlobalScopes,
        Concerns\HasRelationships,
        Concerns\HasTimestamps,
        Concerns\HidesAttributes,
        Concerns\GuardsAttributes,
        ForwardsCalls;


HasEvents.php
namespace Illuminate\Database\Eloquent\Concerns;

use Illuminate\Contracts\Events\Dispatcher;

trait HasEvents
{
    /**
     * The event map for the model.
     *
     * Allows for object-based events for native Eloquent events.
     *
     * @var array
     */
    protected $dispatchesEvents = [];
省略
    /**
     * Get the observable event names.
     *
     * @return array
     */
    public function getObservableEvents()
    {
        return array_merge(
            [
                'retrieved', 'creating', 'created', 'updating', 'updated',
                'saving', 'saved', 'restoring', 'restored',
                'deleting', 'deleted', 'forceDeleted',
            ],
            $this->observables
        );
    }
省略
    protected function registerObserver($class)
    {
        $className = $this->resolveObserverClassName($class);

        // When registering a model observer, we will spin through the possible events
        // and determine if this observer has that method. If it does, we will hook
        // it into the model's event system, making it convenient to watch these.
        foreach ($this->getObservableEvents() as $event) {
            if (method_exists($class, $event)) {
                static::registerModelEvent($event, $className.'@'.$event);
            }
        }
    }

イベントディスパッチャーはIlluminate\Contracts\Events\Dispatcherか。
と思いディレクトリを探すと無い。
そこで気づきました。。イベント設定してもDispatcher無いじゃん。
EventってEloquentが持ってるのかよければ依存で持ってきてると思ったけど。。。違うよね。。

今回はSlimを利用していて、下記のようにDBを読んでいます。


// DIC configuration
$container = $app->getContainer();

$container['db'] = function( $c ) {
	$capsule = new \Illuminate\Database\Capsule\Manager;
	$capsule->addConnection( $c['settings']['db'] );
	$capsule->getConnection()->enableQueryLog();
	$capsule->setAsGlobal();
	$capsule->bootEloquent();
	return $capsule;
};

composer.jsonはこんな感じ


"require": {
	"slim/slim": "^3.12",
	"monolog/monolog": "^1.24",
	"danielstjules/stringy": "^3.1",
	"respect/validation": "^1.1",
	"illuminate/database": "^5.5"
},

さっそくイベントライブラリを追加します。


composer require illuminate/container
composer require illuminate/events

そして、コンテナに入れてるDBにも設定を追加します。
最初間違って、setAsGlobalの後にsetEventDispatcherして動かなくて悩みました。。


use Illuminate\Events\Dispatcher as Dispatcher;
use Illuminate\Container\Container as Container;

// DIC configuration
$container = $app->getContainer();

$container['db'] = function( $c ) {
	$capsule = new \Illuminate\Database\Capsule\Manager;
	$capsule->addConnection( $c['settings']['db'] );
	$capsule->setEventDispatcher( new Dispatcher( new Container ) );
	$capsule->getConnection()->enableQueryLog();
	$capsule->setAsGlobal();
	$capsule->bootEloquent();
	return $capsule;
};

これでSlimフレームワークでもEloquentのModelイベントが動くようになりました。

Slim Frameworkでsettingsに追加した設定値が$c->get(‘settings’)でnull

SlimでTwitterAPIやSessionの設定をsettingsに追加しましたが

コンテナに追加する際に、設定値がNullになっていしまう。


$container['twitterOAuth'] = function ($c) {
    $settings = $c->get('settings')['twitter'];
    var_dump( $c->get('settings') );
    exit;

Null

エラーも特に出てないので、Slimがどうやってsettingsを読んでいるか見てみた。

slim/slim/Slim/Collection.php


他はgetterなどなんで、フィルターはない。
public function __construct(array $items = [])
    {
        $this->replace($items);
    }

slim/slim/Slim/Container.php


settingsが空ならEmptyでCollection作るけど
valuesにはあるけど、userSettingsにはない。
public function __construct(array $values = [])
{
       parent::__construct($values);
var_dump( $values ); ある

       $userSettings = isset($values['settings']) ? $values['settings'] : [];

var_dump( $userSettings );  ない

    $this->registerDefaultServices($userSettings);
}
private function registerDefaultServices($userSettings)
{
    $defaultSettings = $this->defaultSettings;

    /**
     * This service MUST return an array or an
     * instance of \ArrayAccess.
     *
     * @return array|\ArrayAccess
     */
    $this['settings'] = function () use ($userSettings, $defaultSettings) {
        return new Collection(array_merge($defaultSettings, $userSettings));
    };

    $defaultProvider = new DefaultServicesProvider();
    $defaultProvider->register($this);
}

なんてことはなく、settings.phpのsettingsの閉じが意図してなかっただけ。。
return ‘settings’ , ‘session’ , ‘twitter’;
になってただけだった。。

settings.php


// Monolog settings
    'logger' => [
        'name' => 'App',
        'path' => __DIR__ . '/logs/app.log',
        'level' => \Monolog\Logger::DEBUG,
        ],
    ], // 多いい。。。。

tedivm/stash(phpキャッシュライブラリ)でPHP Fatal error: Uncaught Error: Call to protected method

ドキュメント通りにキャッシュディレクトリを指定するとprotected methodだよって怒られます。

https://packagist.org/packages/tedivm/stash


$driver 	= new Stash\Driver\FileSystem();
$driver->setOptions( [ 'path' => dirname(__FILE__) . '/cache/' ] );
$pool = new Stash\Pool( $driver );


PHP Fatal error:  Uncaught Error: Call to protected method Stash\Driver\FileSystem::setOptions() from context ''

コードを見てみると、setOptionsはprotectedに変更されています。


protected function setOptions(array $options = array())

なのでドライバーのインスタンス化の際に指定します。


$driver = new Stash\Driver\FileSystem( [ 'path' => dirname(__FILE__) . '/cache/' ] );
$pool = new Stash\Pool( $driver );

PayPalのPayPal Express Checkoutを使ってみた-サンドボックスの設定からPHPソースまでその1

PayPal決済のExpressCheckoutをPHPで実装するときのメモ

PayPalのエクスプレスチェックアウトとはECサイトなどからPayPalのサイトを経由してPayPalのクレジットカード決済を利用するための仕組み。


PayPalのサイトより


エクスプレス チェックアウトは、PayPalアカウントへ事前に登録したクレジットカード番号や配送先情報を使用して決済をおこないます。そのため、購入者はクレジットカード情報を入力することなく、PayPalアカウントにログインし、シンプルな確認および承認作業をおこなうだけで、決済を完了できます。スムーズな決済フローで、かご落ち率の減少に貢献します。

PayPalにはサンドボックスというテスト環境が用意されているのでまずは、サンドボックすのアカウントをさくせいします。

サンドボックスのアカウントが作成できたら、PayPalのサンドボックスへログインして、Test Accountsを作成します。

アカウントはAccount TypeでBuyer(買い手)、Seller(売り手)を作ることが出来きます。
とりあえず、WEBサイトに組み込んでの決済なので、Website Payments Pro以外の2アカウントがあれば、OKです。

  • Country:国
  • Account Type:ユーザーのタイプ
  • Login Email:ログイン用メルアド
  • Password:パス
  • Add Credit Card:架空のクレジットカード
  • Add Bank Account:架空の銀行口座が設定
  • Account Balance:PayPal口座の残高
  • Notes:メモなので、適当に。



PayPalのテストアカウントができたら、次はソースコードです。
PayPalにはASPやPHP、JAVAなどのソースが公開されていますが、今回はintegrationwizardでPHPのソースコードを作成します。

integrationwizardへアクセスして、ウィザードにしたがってソースを作成していきます。

https://www.paypal-labs.com/integrationwizard/ecpaypal/main.php

今日のところは、ここまでで、くわしくは続きで書きます。