ECのAPIでカード決済を行うには?

KurocoのECモジュールのAPIでカード決済するにはカード番号などの情報を APIに直接渡すのでは無く、決済サービス会社の用意したAPIを利用し カード番号などを元にカードトークン情報を取得し、そのトークン情報を KurocoのAPIに設定する必要があります。

カード利用方法

Kurocoでサポートしている決済サービスはPaygentなので Paygentからカードトークンを取得するためのサンプルコードを記載します。 尚、詳細に関してはPaygent管理画面の「マニュアル/仕様書」より
02_PG外部インターフェース仕様説明書(トークン決済).pdf
をダウンロードして確認してください。

.envファイル設定

キー名
PAYGENT_MARCHANT_IDPaygentの「マーチャントID」
PAYGENT_TOKEN_GENERATE_PUBKEYPaygentの「トークン生成鍵」
PAYGENT_TOKEN_JS開発用)
https://sandbox.paygent.co.jp/js/PaygentToken.js
本番用)
https://token.paygent.co.jp/js/PaygentToken.js

fetched from Gyazo

サンプルコード

components/CreditCardForm.vue
<template>
    <div>
        <vue-form-generator
            ref="form"
            :schema="schema"
            :model="cardData"
            class="c-form"
            @model-updated="onInput"
        />
        <div>
            <button
                type="button"
                @click="subscribe()"
            >
                決済実行
            </button>
        </div>
    </div>
</template>

<script>
import PaygentHelper from '@/util/paygentHelper';

export default Vue.extend({
    name: 'CreditCardForm',
    components: {
        'vue-form-generator': VueFormGenerator.component
    },
    data() {
        return {
            paygent: null,
            cardData: {
                cardNumber: '',
                expireMonth: '',
                expireYear: '',
                cvc: '',
                name: '',
            },
            cardToken: '',
        };
    },
    created() {
        this.paygent = new PaygentHelper();
        this.cardData.expireYear = this.formatYear(new Date().getFullYear() + 1);
    },
    mounted() {
        this.schema = {
            fields: [
                {
                    type: 'vuetifyText',
                    inputType: 'text',
                    min: 0,
                    max: 16,
                    label: 'カード番号',
                    model: 'cardNumber',
                    text: this.cardData.cardNumber,
                    placeholder: '※ハイフンは入力しないでください。',
                    required: true,
                    texttype: 'number',
                    labelClasses: 'required',
                },
                {
                    label: '有効期限',
                    model: 'expireMonth',
                    placeholder: '月',
                    contents: [
                        {
                            key: '01',
                            value: '1月',
                        },
                        (略)
                        {
                            key: '12',
                            value: '12月',
                        },
                    ],
                    option: {
                        key: this.cardData.expireMonth,
                        value: this.cardData.expireMonth + '月',
                    },
                    required: true,
                    labelClasses: 'required',
                    type: 'vuetifySingleOption',
                    styleClasses: 'c-form__twoColumns'
                },
                {
                    model: 'expireYear',
                    label: '',
                    placeholder: '年',
                    contents: [],
                    option: {
                        key: this.cardData.expireYear,
                        value: '20' + this.cardData.expireYear + '年',
                    },
                    required: true,
                    type: 'vuetifySingleOption',
                    styleClasses: 'c-form__twoColumns'
                },
                {
                    model: 'cvc',
                    type: 'vuetifyText',
                    inputType: 'text',
                    min: 3,
                    max: 4,
                    label: 'セキュリティコード',
                    text: this.cardData.cvc,
                    placeholder: '',
                    required: true,
                    labelClasses: 'required',
                },
                {
                    type: 'vuetifyText',
                    inputType: 'text',
                    min: 0,
                    max: 100,
                    label: 'カード名義人',
                    model: 'name',
                    text: this.cardData.name,
                    placeholder: '',
                    required: true,
                    labelClasses: 'required',
                },
            ]
        }

        // 年を自動設定
        this.schema.fields.map((item) => {
            if (item.model === 'expireYear') {
                const year_array = this.arrYear()
                year_array.map((y) => {
                    let option = {
                            key: y,
                            value: '20' + y + '年'
                    }
                    item.contents.push(option)
                })
            }
            return item
        })
    },
    computed: {
        canGenerateToken() {
            const paygentConfigValues = Object.values(this.paygentConfig);
            const cardDataValues = Object.values(this.cardData);
            return (
                paygentConfigValues.filter((v) => v).length === paygentConfigValues.length &&
                cardDataValues.filter((v) => v).length === cardDataValues.length
            );
        },
    },
    methods: {
        onInput (value, fieldName) {
            this.$set(this.cardData, fieldName, value);
        },
        formatYear(year) {
            return ('' + year).slice(-2);
        },
        arrYear() {
            const date = new Date();
            const thisYear = this.formatYear(date.getFullYear());
            const intYear = parseInt(thisYear);
            const years = [];
            for (let y = intYear; y <= intYear + 10; y++) {
                years.push(`${y}`);
            }
            return years;
        },
        async generateToken() {
            this.cardToken = '';
            try {
                const response = await this.paygent.fetchToken(this.cardData);
                this.cardToken = response.cardToken;
            } catch (errorResponse) {
                this.$store.dispatch('snackbar/setError', `[${errorResponse.code}] ${errorResponse.error}`);
                this.$store.dispatch('snackbar/snackOn');
            }
        },
        async subscribe() {
            await this.generateToken()

            if (this.cardToken === '') {
                // トークン取得エラー
                return;
            }

            const self = this;

            this.loading = true

            const cartItem = {
                "product_id": 41202,
                "quantity": 1
            }
            let orderInfo = {
                "order_products": [
                    cartItem
                ],
                "ec_payment_id": 58,
                "card_token": self.cardToken,
            }

            this.$auth.ctx.$axios
            .post('/rcms-api/1/subscribe', orderInfo)
            .then(function (response) {
                alert('購入しました。')
            })
            .catch(function (error) {
                if (error.response) {
                    alert(error.response.data.errors?.[0].message);
                } else {
                    alert("エラーが発生しました");
                }
            });
        }
    },
});
</script>
import { paygentConfig, paygentScriptUrl, paygentErrorCodeDetails } from './paygentConfig';

export default class PaygentHelper {
  constructor(config = null, lang = 'ja') {
    this.config = config ? config : paygentConfig
    this.lang = lang;

    if (!document.body.querySelector(`script[src*='${paygentScriptUrl}']`)) {
      // JS未読み込み
      const script = document.createElement('script');
      script.type = 'text/javascript';
      script.src = paygentScriptUrl;
      document.body.appendChild(script);
      this.script = script;
      this.script.onload = () => {
        this.paygentToken = new PaygentToken();
      };
    } else {
      // JS読み込み済み
      this.paygentToken = new PaygentToken();
    }
  }
  setConfig(config) {
    this.config = config;
  }
  async fetchToken(cardData) {
    return new Promise((resolve, reject) => {
      if (!this.paygentToken) {
        reject({
          code: 'XXXX',
          cardToken: '',
          error: 'Unexpected error',
        });
      }
      this.paygentToken.createToken(
        this.config.merchantId,
        this.config.tokenGeneratePubkey,
        {
          card_number: cardData.cardNumber,
          expire_year: cardData.expireYear,
          expire_month: cardData.expireMonth,
          cvc: cardData.cvc,
          name: cardData.name,
        },
        (response) => {
          const resultCode = response.result || '';
          if (response.result === undefined || response.result !== '0000') {
            reject({
              code: resultCode,
              cardToken: '',
              error: paygentErrorCodeDetails[resultCode]['ja'] || 'Unexpected error',
            });
          } else {
            resolve({
              code: resultCode,
              cardToken: response.tokenizedCardObject.token,
              error: '',
            });
          }
        },
      );
    });
  }
}
export const paygentConfig = {
  merchantId: process.env.PAYGENT_MARCHANT_ID,
  tokenGeneratePubkey: process.env.PAYGENT_TOKEN_GENERATE_PUBKEY,
};

export const paygentScriptUrl = process.env.PAYGENT_TOKEN_JS;
export const paygentErrorCodeDetails = {
  '1100': {
    en: 'Merchant id - Required',
    ja: 'マーチャントID - 必須エラー',
  },
  '1200': {
    en: 'Token generation pubkey - Required',
    ja: 'トークン生成公開鍵 - 必須エラー',
  },
  '1201': {
    en: 'Token generation pubkey - Invalid value',
    ja: 'トークン生成公開鍵 - 不正エラー',
  },
  '1300': {
    en: 'Card number - Required',
    ja: 'カード番号 - 必須チェックエラー',
  },
  '1301': {
    en: 'Card number - Invalid format',
    ja: 'カード番号 - 書式チェックエラー',
  },
  '1400': {
    en: 'Expiration year - Required',
    ja: '有効期限(年) - 必須チェックエラー',
  },
  '1401': {
    en: 'Expiration month - Invalid format',
    ja: '書式チェックエラー - 数字以外が含まれている',
  },
  '1500': {
    en: 'Expiration month - Required',
    ja: '有効期限(月) - 必須チェックエラー',
  },
  '1501': {
    en: 'Expiration month - Invalid format',
    ja: '有効期限(月) - 書式チェックエラー',
  },
  '1502': {
    en: 'Expiration date - Invalid format - The value should be future date and within the next 20 years.',
    ja: '有効期限(年月)が不正です。(過去年月である、未来20年以降である)',
  },
  '1600': {
    en: 'CVC - Invalid format',
    ja: 'セキュリティコード - 書式チェックエラー',
  },
  '1601': {
    en: 'CVC - Required if you use security code token',
    ja: 'セキュリティコード - 必須エラー(セキュリティコードトークンの場合)',
  },
  '1700': {
    en: 'Name - Invalid format',
    ja: 'カード名義 - 書式チェックエラー',
  },
  '7000': {
    en: 'Unsupported browser',
    ja: '非対応のブラウザです。',
  },
  '7001': {
    en: 'Connection failure',
    ja: 'ペイジェントとの通信に失敗しました。',
  },
  '8000': {
    en: 'Under maintenance',
    ja: 'システムメンテナンス中です。',
  },
  '9000': {
    en: 'Internal server error',
    ja: 'ペイジェント決済システム内部エラー',
  },
};

お探しのページは見つかりましたか?解決しない場合は、問い合わせフォームからお問い合わせいただくか、Slackコミュニティにご参加ください。