もがき系プログラマの日常

もがき系エンジニアの勉強したこと、日常のこと、気になっている技術、備忘録などを紹介するブログです。

paypayのAPIを試した

はじめに

こんばんは。

PayPay for Developersがリリースされて、phpSDKもあるらしいので試してみました。

about.paypay.ne.jp

github.com

参考にしたサイト

dev.classmethod.jp

本題

1. PayPay for Developers登録

コチラから新規登録をします。

テストで試すだけなら無料のようです。

登録が済んだら、マーチャントID、API_KEY, API_SECRETが払い出されます。

f:id:kojirooooocks:20200824225208p:plain

f:id:kojirooooocks:20200824225219p:plain

2. SDKインストール

$ composer require paypayopa/php-sdk

3. コード

index.php

他にも設定項目がありますが、一旦これだけにしてます。

<?php

require_once './vendor/autoload.php';

$orderItems = new \Kojirock5260\PayPayExample\OrderItems();
$orderItems->add('商品A', 1, 500);
$orderItems->add('商品B', 3, 1000);
$orderItems->add('商品C', 2, 900);

$config = new \Kojirock5260\PayPayExample\Config([
    'merchantId'      => 'XXXXXXXXXXXXXXX',
    'apiKey'          => 'YYYYYYYYYYYYYY',
    'apiSecret'       => 'ZZZZZZZZZZZZZZZZZ',
    'redirectUrl'     => 'https://kojirooooocks.hatenablog.com/',
    'isAuthorization' => false,
    'production'      => false,
]);

$paypay = new \Kojirock5260\PayPayExample\PayPayAdapter($config);
$result = $paypay->createCode($orderItems, '商品A-商品B-商品C');
var_dump($result);die();

Config.php

設定値を管理するクラスです。

<?php


namespace Kojirock5260\PayPayExample;


class Config
{
    private array $parameters;

    /**
     * Config constructor.
     * @param array $parameters
     */
    public function __construct(array $parameters)
    {
        if (!$this->isValid($parameters)) {
            throw new \InvalidArgumentException('required key invalid');
        }

        $this->parameters = $parameters;
    }

    /**
     * @return string
     */
    public function merchantId(): string
    {
        return $this->parameters['merchantId'];
    }

    /**
     * @return string
     */
    public function apiKey(): string
    {
        return $this->parameters['apiKey'];
    }

    /**
     * @return string
     */
    public function apiSecret(): string
    {
        return $this->parameters['apiSecret'];
    }

    /**
     * @return string
     */
    public function redirectUrl(): string
    {
        return $this->parameters['redirectUrl'];
    }

    /**
     * @return bool
     */
    public function isAuthorization(): bool
    {
        return $this->parameters['isAuthorization'];
    }

    /**
     * @return bool
     */
    public function isProduction(): bool
    {
        return $this->parameters['production'];
    }

    /**
     * @param array $parameters
     * @return bool
     */
    private function isValid(array $parameters): bool
    {
        $keys = [
            'merchantId',
            'apiKey',
            'apiSecret',
            'redirectUrl',
            'isAuthorization',
            'production',
        ];

        foreach ($keys as $key)  {
            if (!isset($parameters[$key])) {
                return false;
            }
        }

        return true;
    }
}

OrderItems.php

購入対象の商品のデータを作成するクラスです。

<?php


namespace Kojirock5260\PayPayExample;


use PayPay\OpenPaymentAPI\Models\OrderItem;

class OrderItems
{
    /** @var OrderItem[] */
    private array $items;

    /**
     * @return array|OrderItem[]
     */
    public function items(): array
    {
        return $this->items;
    }

    /**
     * @param string $name
     * @param int $quantity
     * @param int $amount
     * @throws \Exception
     */
    public function add(string $name, int $quantity, int $amount): void
    {
        $this->items[] = (new OrderItem())
            ->setName($name)
            ->setQuantity($quantity)
            ->setUnitPrice(['amount' => $amount, 'currency' => 'JPY']);
    }

    /**
     * @return int
     */
    public function total(): int
    {
        $result = 0;
        foreach ($this->items as $item) {
            $result += $item->getQuantity() * $item->getUnitPrice()['amount'];
        }

        return $result;
    }
}

PayPayAdapter.php

PayPayAPIを実行するクラスです。

<?php


namespace Kojirock5260\PayPayExample;


use PayPay\OpenPaymentAPI\Client;
use PayPay\OpenPaymentAPI\Models\CreateQrCodePayload;

class PayPayAdapter
{
    private Config $config;

    /**
     * PayPayAdapter constructor.
     * @param Config $config
     */
    public function __construct(Config $config)
    {
        $this->config = $config;
    }

    /**
     * @param OrderItems $orderItems
     * @param string     $paymentId
     * @return mixed
     * @throws \Exception
     */
    public function createCode(OrderItems $orderItems, string $paymentId)
    {
        $client = new Client([
            'API_KEY'     => $this->config->apiKey(),
            'API_SECRET'  => $this->config->apiSecret(),
            'MERCHANT_ID' => $this->config->merchantId(),
        ], $this->config->isProduction());

        $payload = new CreateQrCodePayload();
        $payload->setOrderItems($orderItems->items());
        $payload->setMerchantPaymentId($paymentId);
        $payload->setCodeType("ORDER_QR");
        $payload->setAmount(['amount' => $orderItems->total(), 'currency' => 'JPY']);
        $payload->setRedirectType('WEB_LINK');
        $payload->setIsAuthorization($this->config->isAuthorization());
        $payload->setRedirectUrl($this->config->redirectUrl());

        return $client->code->createQRCode($payload);
    }
}

4. 実行

ターミナルで実行してみると、var_dump している箇所でいろんなデータがありますが、以下のようなデータが存在します。

$ php index.php
  'data' =>
  array(11) {
    ...
    'url' =>
    string(60) "https://qr-stg.sandbox.paypay.ne.jp/28180104LsJmN9v8wGTniY3O"
    ...

このURLが実際に作成されたQRコードを表示してくれるページです。

f:id:kojirooooocks:20200824225953p:plain

この作成されたQRの有効期限は5分間です。

ちなみに有効期限もデータとして返却されてます。

このQRを読み込むには、PayPayのテストアカウントが必要なのですが、現在はテストアカウントが公開されていないため、実際には読み込めません。

f:id:kojirooooocks:20200824230213p:plain

終わりに

コレ以上進められなかったのが残念でした...

ストアカウントの情報がアップされたらまた踏み込んで試そうと思います。

今までのコードは githubにあげておきました。

github.com

現場からは以上です。

github actionで git-pr-release

はじめに

こんばんは。

github actionとgit-pr-release使ってますか?

今関わっている案件でもバリバリ使ってます。

こんかいは、完全にこちらの記事の基本版のコードを再現させていただきました。ありがとうございます。

qiita.com

本題

name: Create a release pull request

on:
  push:
    branches:
      - develop

jobs:
  create-release-pr:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
      with:
        fetch-depth: 0

    - name: Set up Ruby 2.6
      uses: actions/setup-ruby@v1
      with:
        ruby-version: 2.6.x

    - name: Create a release pull request
      env:
        GIT_PR_RELEASE_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        GIT_PR_RELEASE_BRANCH_PRODUCTION: master
        GIT_PR_RELEASE_BRANCH_STAGING: develop
        GIT_PR_RELEASE_LABELS: release
      run: |
        gem install -N git-pr-release -v "1.2.0"
        git-pr-release --no-fetch

f:id:kojirooooocks:20200823224636p:plain

f:id:kojirooooocks:20200823224855p:plain

一個だけハマったのが、今までだと、develop -> feature/** ブランチをつくって developへのPRを作成したさい、 Squash And merge を実行していたのですが、それだと git-pr-releaseが反応しませんでした。

Merge Commitがあるかどうかで判定しているようなので、基本的には Create a merge commit でマージしています。

f:id:kojirooooocks:20200823225144p:plain

終わりに

今週前半子供の風邪が移って完全にダウンしていたので、ブログかけないかと思いましたが、しょうもないブログで申し訳ないですが、なんとかかけました。

現場からは以上です。

vueで画像 upload

はじめに

こんばんは。

今回はVueで画像のアップロードを試してみました。

参考にさせてもらったのはこちらです。

スタイルは前回同様 tailwindcss を使用しています。

本題

今回も、前回同様 vee-validateをかけ合わせた componentを使用しています。

<template>
  <div class="mb-4">
    <validation-provider
      v-slot="{ errors, validate }"
      :rules="internalValidateRule"
    >
      <label class="block text-sm font-bold mb-2">{{ label }}</label>

      <label
        class="upload-field inline-block cursor-pointer rounded text-white bg-blue-600 hover:bg-blue-800"
      >
        <input
          v-if="reset"
          type="file"
          @change="imageUpload($event, errors, validate)"
        />画像を選択
      </label>

      <div
        v-show="internalValue"
        class="max-w-sm h-64 rounded overflow-hidden shadow-lg mt-2"
      >
        <img class="w-full" :src="internalValue" />
      </div>
      <div
        v-show="internalValue"
        class="inline-block bg-red-200 hover:bg-red-400 rounded px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mt-2 cursor-pointer"
        @click="remove"
      >
        close
      </div>
      <div class="text-red-500">
        {{ errors[0] }}
      </div>
    </validation-provider>
  </div>
</template>

<script>
export default {
  name: 'ImageUpload',
  props: {
    value: {
      type: String,
      default: ''
    },
    label: {
      type: String,
      default: 'テキスト'
    },
    validateRules: {
      type: String,
      default: null
    }
  },
  data() {
    return {
      reset: true,
      maxSize: 20 * 1024 ** 5,
      defaultValidateRules: 'image|ext:jpeg,jpg,png'
    }
  },
  computed: {
    internalValidateRule() {
      if (this.validateRules && this.validateRules.length !== null) {
        return `${this.validateRules}|${this.defaultValidateRules}`
      }
      return this.defaultValidateRules
    },
    internalValue: {
      get() {
        return this.value
      },
      set(newValue) {
        if (this.value !== newValue) {
          this.$emit('update:value', newValue)
        }
      }
    }
  },
  methods: {
    async imageUpload(e, errors, validate) {
      const result = await validate(e)
      if (!result.valid) {
        errors.push(result.errors[0])
        return
      }

      const files = e.target.files || e.dataTransfer.files

      const filesSize = files.length
        ? Array.from(files).reduce((previous, current) => (previous += current.size), 0)
        : 0

      if (filesSize > this.maxSize) {
        errors.push('アップロードできるファイルサイズは5Mまでです')
        return
      }

      this.createImage(files[0])
    },
    createImage(file) {
      const reader = new FileReader()
      reader.onload = e => {
        this.internalValue = e.target.result
      }
      reader.readAsDataURL(file)
    },
    remove() {
      this.internalValue = ''
      this.reset = false
      this.$nextTick(function() {
        this.reset = true
      })
    }
  }
}
</script>

<style scoped>
.upload-field {
  padding: 10px 30px;
}

input[type='file'] {
  display: none;
}
</style>

gyazo.com

ファイルサイズの制限、close後のfile.valueのリセットなど、ググりまくってコレに落ち着きました。 また、vee-validateで mimetypeの制限をかけたいときに @changeイベントが先に動いて、validateが動かないという現象があったので、changeイベント先のメソッド内で直接 validationを実行して、エラーを取るようにしました。

一旦コレで問題なく動いていますが、また変な動きが出てきた場合は修正するかもです。

終わりに

前回のdatepickerと違って、しっくり来るライブラリがなかったので、かなり探して今の形に落ち着きました。 実際ガッツリ使い始めると、また不具合出てくるかもしれませんが、、、

画像アップロードならこういうライブラリおすすめだよ!みたいなのがあったらぜひ教えてほしいです!

vue-ctk-date-time-picker便利

はじめに

こんばんは。 最近vue触ってます。

今回はDateTimePicker系の便利なライブラリである vue-ctk-date-time-picker です。

github.com

スタイルは tailwindcss を使用しています。

本題

いろいろなオプションの指定がありますが、そのへんは端折って、自分は以下みたいな感じで component可しています。

<template>
  <div class="mb-4">
    <validation-provider v-slot="{ errors }" :rules="validateRules">
      <label class="block text-sm font-bold mb-2">{{ label }}</label>
      <vue-ctk-date-time-picker
        v-model="internalValue"
        :disabled="isDisable"
        :no-label="true"
        :no-header="true"
        :no-button-now="true"
        :formatted="'YYYY-MM-DD HH:mm'"
        :format="'YYYY-MM-DD HH:mm'"
        :minute-interval="minuteStep"
        :label="placeHolder"
      />
      <p v-if="helpText.length > 0" class="text-sm text-gray-600">
        {{ helpText }}
      </p>
      <span class="text-red-500">
        {{ errors[0] }}
      </span>
    </validation-provider>
  </div>
</template>

<script>
export default {
  name: 'DateTimePicker',
  props: {
    value: {
      type: String,
      default: ''
    },
    label: {
      type: String,
      default: 'テキスト'
    },
    validateRules: {
      type: String,
      default: null
    },
    isDisable: {
      type: Boolean,
      default: false
    },
    helpText: {
      type: String,
      default: ''
    },
    placeHolder: {
      type: String,
      default: '選択してください'
    },
    minuteStep: {
      type: Number,
      default: 30
    }
  },
  computed: {
    internalValue: {
      get() {
        return this.value
      },
      set(newValue) {
        if (this.value !== newValue) {
          this.$emit('update:value', newValue)
        }
      }
    }
  }
}
</script>

vee-validateも一緒に使用しているので、vue-ctk-date-time-picker以外の情報もありますが、このような形にしています。

gyazo.com

いい感じです。 月が漢字なのが、ちょっと違和感ですが、非常に使いやすいです。

また、日付のみのDatePickerComponentも作りました。オプションで指定しているだけですが...w

gyazo.com

紹介されている demoサイトでオプションの詳細が確認できます。

chronotruck.github.io

終わりに

ちょっとサイズが大きいのが何点ですが、すごく使いやすいので、今後もコレもっと使っていきたいと思います。

現場からは以上です。

awsのパラメータストア便利

はじめに

こんばんは。

めちゃめちゃ何番煎じかというはなしですが、すごく便利だったのでブログにしときます。

データベースのパスワードとか、AWSのアクセスキーとかどうやってもたせたらいいかなーといつも思ってて、色々調べてたんですが、パラメータストアがとても便利でした。

参考サイト

使ってますか? パラメータストア

本題

こんな感じで登録しました。

f:id:kojirooooocks:20200802224934p:plain

すごく便利だなーと思ったのは、 階層でもてるというところでした。

画像でいうと /Staging という感じで、ステージング環境で使うパラメータとして登録できます。

本番で使いたい場合は /Production みたいな感じで登録できます。

このデータを取りたい場合、以下みたいなコマンドになると思います。

$ aws ssm get-parameters --name '/Staging/AWS_ACCESS_KEY_ID'  --with-decryption | jq -r '.Parameters[].Value'

例えば、この取得したデータを .envに追記する場合とかは以下になるとおもいます。

$ echo -e "AWS_ACCESS_KEY_ID=`aws ssm get-parameters --name '/Staging/AWS_ACCESS_KEY_ID' --with-decryption | jq -r '.Parameters[].Value'`" >> .env

この時点ですげー便利だと自分は思ったんですけど、一気にとって一気に .env に登録できないかなーと思って調べたらできました。

$ aws ssm get-parameters-by-path --path '/Staging' --with-decryption | jq -r '.Parameters | map([.Name[9:], .Value] | join("=")) | join("\n")' >> .env

これで /Staging の階層に入っているパラメータをすべて取得して .envに追加できます。

パラメータストアが便利っていうのもありますが、jqがとにかく便利というのを再確認できました...w

終わりに

久々に awsさわって、インフラ屋さんが神だということを再認識しました。

現場からは以上です。

laravelのvalidationのuniqueでsoftDeleteされたものを除外する

はじめに

こんばんは。簡単ですが、ちょっと考えたところがあったので、ブログに残しておきます。

laravelのvalidationのお話です。

readouble.com

本題

laravelのvalidationで、uniqueがあると思ういます。

例えば、 emailカラムをuniqueにしたい場合は、例えば required|unique:users,email みたいな感じでルールを記載できると思います。

新規登録の際は問題ないと思うんですが、編集の際は、この同じルールを使うと、既に存在しているのでバリデーションに引っかかり怒られます。

これを解除するには、以下みたいな感じで、このIDは除外するという方法がありました。

例えば以下は、自身のID以外がuniqueの対象になるという感じです。

<?php
  return [
    'email'  => ['required', Rule::unique('users', 'email')->ignore(Auth::id())],
  ];

今回は、論理削除を使っているテーブルで、既に論理削除済みの場合は、uniqueルールから除外したいという感じでした。

調べてたらドンピシャな感じのがありました。

<?php
  return [
    'email' => ['required', Rule::unique('users', 'email')->where(static function ($query) {
         return $query->whereNull('deleted_at');
    })]
  ];

もちろん除外IDの ignore() もメソッドチェーンで一緒にかけます。

これで、自分がやりたかったことは実現できました!

終わりに

簡単ですが、今回はこれで終わりです!

最近筋トレやり始めましたが、いつまで続くか。。。

がんばります。。。

テスト駆動開発輪読会 Vol.1

はじめに

こんばんは。 先週、テスト駆動開発の輪読会をおこないました。

yourmystar.connpass.com

テスト駆動開発

テスト駆動開発

vol1の発表は自分が担当しました。

本題

speakerdeck.com

1章〜5章という範囲で、内容も写経メインだったので、少し内容的に薄かったですが、輪読会後の皆さんとのディスカッションがとてもおもしろかったです。

記憶しているところでいうと以下でした。

  1. 実際テストを書いていて「テストを書いていてよかった」と感じるのはどの程度あるのか
  2. カバレッジのパーセンテージの考え方
  3. 先にテストを書くという考えについて

技術レベルも、分野も違う人たちでお話したので、とても刺激的でした。

終わりに

すでに第2回が開催予定なので、興味がある方はぜひ

yourmystar.connpass.com