Skip to main content

How can I process card payments with the EC API?

To process card payments with Kuroco's EC module API, you must not pass card numbers and other card information directly to the API. Instead, use the API provided by your payment service to obtain a card token from the card information, then set that token in the Kuroco API.

How to use cards

Kuroco supports Paygent as its payment service, so this section provides sample code for obtaining a card token from Paygent. For details, download 02_PG外部インターフェース仕様説明書(トークン決済).pdf ("PG External Interface Specification (Token Payment)") from the "Manuals/Specifications" section of the Paygent admin panel.

.env file configuration

KeyValue
PAYGENT_MARCHANT_IDPaygent's "Merchant ID"
PAYGENT_TOKEN_GENERATE_PUBKEYPaygent's "Token generation key"
PAYGENT_TOKEN_JSFor development:
https://sandbox.paygent.co.jp/js/PaygentToken.js
For production:
https://token.paygent.co.jp/js/PaygentToken.js

fetched from Gyazo

Sample code

/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月',
},
// (omitted)
{
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',
},
]
}

// Automatically set the year
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 === '') {
// Token retrieval error
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>
paygentHelper.js
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}']`)) {
// Script not loaded yet
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 {
// Script already loaded
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: 'ペイジェント決済システム内部エラー',
},
};

Support

If you have any other questions, please contact us or check out Our Slack Community.