Samsung Wallet CDATA (JWT) generator for Node.js - Generate encrypted card data for Samsung Wallet API
npm install samsung-wallet-cdata๐ฐ๐ท ํ๊ตญ์ด | ๐บ๐ธ English
> โ ๏ธ Unofficial - ์ด ํจํค์ง๋ Samsung์ ๊ณต์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์๋๋๋ค.
> Samsung Wallet API ์ฐ๋์ ์ํ ์ปค๋ฎค๋ํฐ ์คํ์์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์
๋๋ค.
Samsung Wallet CDATA(JWT) ์์ฑ ๋ผ์ด๋ธ๋ฌ๋ฆฌ for Node.js
Samsung Wallet API ์ฐ๋์ ์ํ CDATA๋ฅผ ์ฝ๊ฒ ์์ฑํ ์ ์์ต๋๋ค.
Samsung์ ๊ณต์์ ์ผ๋ก Java ์ํ ์ฝ๋๋ง ์ ๊ณตํฉ๋๋ค:
- ๊ณต์ CDATA Generation Sample Code (Java)
Node.js ๊ฐ๋ฐ์๋ฅผ ์ํ ๊ณต์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์์ด์ ์ด ํจํค์ง๋ฅผ ๋ง๋ค์์ต๋๋ค.
Samsung ๊ณต์ Java ์ฝ๋ cdata_generation_sample_code_v1.1.2๋ฅผ Node.js๋ก ํฌํ
ํ์ต๋๋ค.
``bash`
npm install samsung-wallet-cdata
`javascript
const { SamsungWallet } = require('samsung-wallet-cdata');
const fs = require('fs');
// 1. ์ธ์คํด์ค ์์ฑ (์ค์ ํ๋ฒ๋ง)
const wallet = new SamsungWallet({
partnerId: 'YOUR_PARTNER_ID',
certificateId: 'YOUR_CERT_ID',
samsungCert: fs.readFileSync('samsung.crt', 'utf-8'),
partnerKey: fs.readFileSync('partner.key', 'utf-8'),
});
// 2. ํฐ์ผ CDATA ์์ฑ (ํ ์ค!)
const cdata = await wallet.createTicket({
title: '์ฝ์ํธ ์
์ฅ๊ถ',
holderName: 'ํ๊ธธ๋',
startDate: new Date('2026-03-15T19:00:00'),
location: '์ฌ๋ฆผํฝ ์ฒด์กฐ๊ฒฝ๊ธฐ์ฅ',
barcode: 'TICKET-12345',
});
console.log('CDATA:', cdata);
`
์ฝ์ํธ, ์ ์ํ, ์คํฌ์ธ ๊ฒฝ๊ธฐ ๋ฑ์ ์ ์ฅ๊ถ
`javascript`
const cdata = await wallet.createTicket({
// ํ์
title: '๋ฎค์ง์ปฌ ์บฃ์ธ ',
// ์ ํ (์ถ์ฒ)
holderName: 'ํ๊ธธ๋',
startDate: new Date('2026-03-15T19:00:00'),
endDate: new Date('2026-03-15T22:00:00'),
location: '๋ธ๋ฃจ์คํ์ด ์ ํ์นด๋ํ',
barcode: 'TICKET-12345-ABCD',
// ๋ธ๋๋ฉ
providerName: 'TicketLink',
logoImage: 'https://example.com/logo.png',
mainImage: 'https://example.com/main.png',
bgColor: '#1428A0',
// ์ข์ ์ ๋ณด
seatClass: 'VIP์',
seatNumber: 'A์ด 15๋ฒ',
});
ํ์๊ถ, ํฌ์ธํธ ์นด๋, ๋ฉค๋ฒ์ญ ์นด๋
`javascript`
const cdata = await wallet.createMembership({
title: 'VIP ๋ฉค๋ฒ์ญ',
memberName: 'ํ๊ธธ๋',
memberId: 'M-2026-12345',
tier: 'Gold',
points: 15000,
expiryDate: new Date('2027-12-31'),
providerName: 'My Store',
bgColor: '#FFD700',
barcode: 'MEMBER-12345',
});
ํ ์ธ ์ฟ ํฐ, ๊ธฐํํธ ์นด๋
`javascript`
const cdata = await wallet.createCoupon({
title: '20% ํ ์ธ ์ฟ ํฐ',
description: '์ ํ๋ชฉ ์ ์ฉ ๊ฐ๋ฅ',
discount: '20% OFF',
expiryDate: new Date('2026-12-31'),
providerName: 'My Store',
bgColor: '#FF6B35',
barcode: 'COUPON-2026-SAVE20',
terms: [
'๋ค๋ฅธ ์ฟ ํฐ๊ณผ ์ค๋ณต ์ฌ์ฉ ๋ถ๊ฐ',
'์ต์ ์ฃผ๋ฌธ๊ธ์ก 30,000์ ์ด์',
],
});
ํญ๊ณต๊ถ, ๋ฒ์ค/๊ธฐ์ฐจ ํฐ์ผ
`javascript`
const cdata = await wallet.createBoardingPass({
title: 'KE123',
user: 'HONG/GILDONG',
vehicleNumber: 'KE123',
departCode: 'ICN',
arriveCode: 'NRT',
departName: '์ธ์ฒ๊ตญ์ ๊ณตํญ',
arriveName: '๋๋ฆฌํ๊ตญ์ ๊ณตํญ',
estimatedOrActualStartDate: new Date('2026-03-15T10:30:00'),
departGate: 'A12',
seatNumber: '23A',
seatClass: 'Economy',
providerName: 'Korean Air',
barcodeValue: 'BCBP-DATA-HERE',
});
์ํ๊ถ, ๊ธฐํํธ ์นด๋, ์คํ ์ด ํฌ๋ ๋ง
`javascript`
const cdata = await wallet.createGiftCard({
title: '์ปคํผ์ ๊ธฐํํธ์นด๋',
providerName: '์คํ๋ฒ
์ค',
cardNumber: 'GC-2026-12345',
pin: '1234',
balance: '50000',
balanceCurrency: 'KRW',
expiryDate: new Date('2027-12-31'),
user: 'ํ๊ธธ๋',
senderName: '๊น์ฒ ์',
message: '์์ผ ์ถํํด์!',
'barcode.value': 'GC-2026-12345',
});
์ฌ์์ฆ, ํ์์ฆ, ์ถ์ ์ฆ
`javascript`
const cdata = await wallet.createDigitalId({
title: '์ฌ์์ฆ',
providerName: '์ผ์ฑ์ ์',
subType: 'employee',
user: 'ํ๊ธธ๋',
idNumber: 'EMP-2026-12345',
department: '๊ฐ๋ฐํ',
position: '์๋์ด ๊ฐ๋ฐ์',
issueDate: new Date('2026-01-01'),
expiryDate: new Date('2027-12-31'),
'barcode.value': 'EMP-2026-12345',
});
๊ตํต์นด๋, ์ ๋ถ ํต์ ์๊ธ, ์ ํธ๋ฆฌํฐ ํ์ด๋จผํธ
`javascript`
const cdata = await wallet.createPayAsYouGo({
title: '์งํ์ฒ ๊ตํต์นด๋',
providerName: '์์ธ๊ตํต๊ณต์ฌ',
subType: 'transit',
accountId: 'METRO-2026-12345',
balance: '25500',
balanceCurrency: 'KRW',
user: 'ํ๊ธธ๋',
'barcode.value': 'METRO-2026-12345',
});
์ ํ์ ์ ํด๋นํ์ง ์๋ ๋ฒ์ฉ ์นด๋
`javascript`
const cdata = await wallet.createGeneric({
title: '์ฃผ์ฐจ๊ถ',
providerName: '์ํฐํํน',
subtitle: '์๊ฐ ์ฃผ์ฐจ ํ๊ฐ์ฆ',
user: 'ํ๊ธธ๋',
cardNumber: 'PARK-2026-12345',
header1: 'A๊ตฌ์ญ',
body1: 'A๊ตฌ์ญ ์ข
์ผ ์ฃผ์ฐจ ๊ฐ๋ฅ',
issueDate: new Date('2026-01-01'),
expiryDate: new Date('2026-12-31'),
'barcode.value': 'PARK-2026-12345',
});
ํ ํ๋ฆฟ์ ์ฌ์ฉํ์ง ์๊ณ ์ง์ ์นด๋ ๋ฐ์ดํฐ๋ฅผ ๊ตฌ์ฑํ ์๋ ์์ต๋๋ค:
`javascript
const cardData = {
card: {
type: 'ticket',
subType: 'entrances',
data: [{
refId: 'my-unique-ref-id',
createdAt: Date.now(),
updatedAt: Date.now(),
language: 'en',
attributes: {
title: 'My Custom Ticket',
// ... ๊ธฐํ ์์ฑ
},
}],
},
};
const cdata = await wallet.generateCdata(cardData);
`
CDATA์ ์ ์ฒด ์นด๋ ๋ฐ์ดํฐ๋ฅผ ํฌํจํ๋ ๋ฐฉ์์ ๋๋ค.
`javascript
const cdata = await wallet.createTicket({ ... });
// Samsung Wallet ์ถ๊ฐ ๋ฒํผ URL ์์ฑ
// ํ์: https://a.swallet.link/atw/v3/{cardId}#Clip?cdata={cdata}
const addUrl = wallet.getAddToWalletUrl(cdata, 'YOUR_CARD_ID', {
rdClickUrl: 'https://your-tracking.com/click',
});
console.log('Add to Wallet URL:', addUrl);
`
๋ฏผ๊ฐํ ๋ฐ์ดํฐ๊ฐ ์๊ฑฐ๋ ์ ์ ๋งํฌ๊ฐ ํ์ํ ๋ ์ฌ์ฉํฉ๋๋ค.
์ด ๋ฐฉ์์ refId๋ง ์ ๋ฌํ๊ณ , Samsung ์๋ฒ๊ฐ ํํธ๋ ์๋ฒ์ Get Card Data API๋ฅผ ํธ์ถํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
`javascript
// Data Fetch Link URL ์์ฑ
// ํ์: https://a.swallet.link/atw/v3/{certificateId}/{cardId}#Clip?pdata={refId}
const fetchUrl = wallet.getDataFetchUrl('TICKET-12345', 'YOUR_CARD_ID');
console.log('Data Fetch URL:', fetchUrl);
`
> โ ๏ธ ์ฃผ์: Data Fetch Link ์ฌ์ฉ ์ ํํธ๋ ์๋ฒ์์ Get Card Data API๋ฅผ ๊ตฌํํด์ผ ํฉ๋๋ค.
Samsung Wallet์ ๋ฑ๋ก๋ ์นด๋์ ์ํ๋ฅผ ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
์นด๋ ์ ๋ณด๊ฐ ๋ณ๊ฒฝ๋์์ ๋ Samsung์ ์๋ฆผ์ ๋ณด๋ ๋๋ค.
`javascript
// Send Card State์์ ๋ฐ์ callbackUrl ์ฌ์ฉ
const callbackUrl = 'https://kr.swallet.link/wltex/cards/YOUR_CARD_ID';
const result = await wallet.sendUpdateNotification(
callbackUrl,
'ticket', // ์นด๋ ํ์
[{ refId: 'TICKET-12345' }]
);
console.log('Update ๊ฒฐ๊ณผ:', result.ok);
`
๊ณต์ฐ/์ด๋ฒคํธ ์ทจ์ ์ Samsung Wallet ์นด๋๋ ๋ง๋ฃ ์ฒ๋ฆฌํฉ๋๋ค.
`javascript
const result = await wallet.sendCancelNotification(
callbackUrl,
'ticket',
[{
refId: 'TICKET-12345',
eventId: 'EVENT-001',
estimatedOrActualStartDate: Date.now(),
}]
);
console.log('Cancel ๊ฒฐ๊ณผ:', result.ok);
`
Samsung Server API ํธ์ถ ์ ํ์ํ ์ธ์ฆ ํ ํฐ์ ์์ฑํฉ๋๋ค.
`javascript`
const authToken = await wallet.generateAuthToken();
console.log('Auth Token:', authToken);
ํจํค์ง์์ ์ ๊ณตํ๋ ์์๋ฅผ ์ฌ์ฉํ๋ฉด ํ์ ์์ ์ฑ์ ๋์ผ ์ ์์ต๋๋ค:
`javascript
const {
SamsungWallet,
CardType,
CardState,
BarcodeType,
TicketSubType
} = require('samsung-wallet-cdata');
// ์นด๋ ํ์
ํ์ธ
console.log(CardType.TICKET); // 'ticket'
console.log(CardType.COUPON); // 'coupon'
// ์นด๋ ์ํ
console.log(CardState.CANCELED); // 'CANCELED'
// ๋ฐ์ฝ๋ ํ์
console.log(BarcodeType.QRCODE); // 'QRCODE'
// ํฐ์ผ ์๋ธํ์
console.log(TicketSubType.CONCERTS); // 'concerts'
`
| ์์ | ์ค๋ช
|
|------|------|
| CardType | ์นด๋ ํ์
(TICKET, COUPON, LOYALTY, BOARDING_PASS, GIFT_CARD, DIGITAL_ID, PAY_AS_YOU_GO, GENERIC) |CardState
| | ์นด๋ ์ํ (ACTIVE, EXPIRED, CANCELED, DELETED, REDEEMED, SUSPENDED) |BarcodeType
| | ๋ฐ์ฝ๋ ํ์
(QRCODE, BARCODE, SERIAL) |BarcodeFormat
| | ๋ฐ์ฝ๋ ํ์ (QRCODE_SERIAL, QRCODE_URL, BARCODE_SERIAL) |BarcodeSubFormat
| | ๋ฐ์ฝ๋ ์ธ๋ถ ํ์ (QR_CODE, CODE_128, CODE_39, EAN_13 ๋ฑ) |TicketSubType
| | ํฐ์ผ ์๋ธํ์
(ENTRANCES, CONCERTS, SPORTS, MOVIES, TRANSIT, OTHERS) |BoardingPassSubType
| | ๋ณด๋ฉํจ์ค ์๋ธํ์
(AIRLINES, TRAINS, BUSES, OTHERS) |DigitalIdSubType
| | ๋์งํธID ์๋ธํ์
(EMPLOYEE, STUDENT, ACCESS, OTHERS) |PayAsYouGoSubType
| | ์ ๋ถ์นด๋ ์๋ธํ์
(TRANSIT, TELECOM, UTILITY, OTHERS) |
์ง์ ์ํธํ/์๋ช ๋ก์ง์ ์ฌ์ฉํ ์ ์์ต๋๋ค:
`javascript
const {
generateCdata,
readCertificate,
readPrivateKey
} = require('samsung-wallet-cdata');
// ํค ๋ก๋
const samsungPublicKey = await readCertificate(samsungCertPem);
const partnerPrivateKey = await readPrivateKey(partnerKeyPem);
// CDATA ์์ฑ
const cdata = await generateCdata(
partnerId,
certificateId,
samsungPublicKey,
partnerPrivateKey,
JSON.stringify(cardData)
);
`
Samsung Wallet API๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ํํธ๋ ๋ฑ๋ก ํ ์ธ์ฆ์๋ฅผ ๋ฐ๊ธ๋ฐ์์ผ ํฉ๋๋ค.
#### Step 1: ํํธ๋ ๋ฑ๋ก
1. Samsung Wallet Partner Portal ์ ์
2. "Sign Up" ํด๋ฆญํ์ฌ ๊ณ์ ์์ฑ
3. ํํธ๋ ์ ์ฒญ์ ์์ฑ
4. ์น์ธ ๋๊ธฐ (๋ณดํต 1-3 ์์
์ผ)
#### Step 2: Wallet Card ์์ฑ
1. Partner Portal ๋ก๊ทธ์ธ
2. "Wallet Cards" โ "Create Card" ๋ฉ๋ด๋ก ์ด๋
3. ์นด๋ ํ์
์ ํ (Ticket, Membership, Coupon ๋ฑ)
4. ์นด๋ ๋์์ธ ๋ฐ ํ๋ ์ค์
5. ์ ์ฅ ํ Card ID ๋ฉ๋ชจ
#### Step 3: ์ธ์ฆ์ ์์ฑ
1. Partner Portal์์ "Certificates" ๋ฉ๋ด๋ก ์ด๋
2. CSR (Certificate Signing Request) ์์ฑ:
`bash`
# ๊ฐ์ธํค ์์ฑ
openssl genrsa -out partner.key 2048
# CSR ์์ฑ
openssl req -new -key partner.key -out partner.csr
partner.csr
3. ์ Partner Portal์ ์
๋ก๋
4. ์๋ช
๋ ์ธ์ฆ์์ Samsung ๊ณต๊ฐ ์ธ์ฆ์ ๋ค์ด๋ก๋
#### Step 4: ๋ฐ๊ธ๋ฐ์ ์ ๋ณด ์ ๋ฆฌ
์น์ธ ํ ๋ค์ ์ ๋ณด๋ค์ ํ๋ณดํ๊ฒ ๋ฉ๋๋ค:
| ํญ๋ชฉ | ํ์ธ ์์น | ํ์ผ/๊ฐ |
|------|----------|---------|
| Partner ID | Partner Portal โ Profile | 1234567890 |cert-xxxxx
| Certificate ID | Partner Portal โ Certificates | |card-xxxxx
| Card ID | Partner Portal โ Wallet Cards | |samsung.crt
| Samsung ์ธ์ฆ์ | Partner Portal โ ๋ค์ด๋ก๋ | |partner.key
| ํํธ๋ ๊ฐ์ธํค | ๋ก์ปฌ (Step 3์์ ์์ฑ) | |
#### ํ์ผ ๊ตฌ์กฐ ์์
``
your-project/
โโโ certs/
โ โโโ samsung.crt # Samsung ๊ณต๊ฐ ์ธ์ฆ์
โ โโโ partner.key # ํํธ๋ ๊ฐ์ธํค (๋น๋ฐ ์ ์ง!)
โโโ .env
โโโ ...
> โ ๏ธ ๋ณด์ ์ฃผ์: partner.key๋ ์ ๋ ๋ฒ์ ๊ด๋ฆฌ(Git)์ ์ปค๋ฐํ์ง ๋ง์ธ์!
`env`
SAMSUNG_WALLET_PARTNER_ID=your-partner-id
SAMSUNG_WALLET_CERTIFICATE_ID=your-cert-id
SAMSUNG_WALLET_CARD_ID=your-card-id
TypeScript ํ์ ์ ์๊ฐ ํฌํจ๋์ด ์์ต๋๋ค:
`typescript
import { SamsungWallet, TicketOptions } from 'samsung-wallet-cdata';
const wallet = new SamsungWallet({
partnerId: 'xxx',
certificateId: 'xxx',
samsungCert: '...',
partnerKey: '...',
});
const options: TicketOptions = {
title: 'Concert',
barcode: '12345',
};
const cdata = await wallet.createTicket(options);
`
| ๋ฉ์๋ | ์ค๋ช
| ๊ณต์ ์คํ |
|--------|------|----------|
| constructor(config) | ์ธ์คํด์ค ์์ฑ | - |generateCdata(cardData)
| | ๋ฒ์ฉ CDATA ์์ฑ | - |createTicket(options)
| | ํฐ์ผ CDATA ์์ฑ | Event Ticket |createMembership(options)
| | ๋ฉค๋ฒ์ญ CDATA ์์ฑ | Loyalty |createCoupon(options)
| | ์ฟ ํฐ CDATA ์์ฑ | Coupon |createBoardingPass(options)
| | ๋ณด๋ฉํจ์ค CDATA ์์ฑ | Boarding Pass |createGiftCard(options)
| | ๊ธฐํํธ์นด๋ CDATA ์์ฑ | Gift Card |createDigitalId(options)
| | ๋์งํธID CDATA ์์ฑ | Digital IDs |createPayAsYouGo(options)
| | ์ ๋ถ์นด๋ CDATA ์์ฑ | Pay As You Go |createGeneric(options)
| | ์ผ๋ฐ์นด๋ CDATA ์์ฑ | Generic Card |getAddToWalletUrl(cdata, cardId, options)
| | Data Transmit Link URL ์์ฑ | API Guidelines |getDataFetchUrl(refId, cardId, options)
| | Data Fetch Link URL ์์ฑ | API Guidelines |generateAuthToken()
| | Samsung Server API ์ธ์ฆ ํ ํฐ ์์ฑ | Security |sendUpdateNotification(callbackUrl, cardType, cardDataList)
| | ์นด๋ ์
๋ฐ์ดํธ ์๋ฆผ | API Guidelines |sendCancelNotification(callbackUrl, cardType, cancelDataList)` | ์นด๋ ์ทจ์ ์๋ฆผ | API Guidelines |
|
Samsung Wallet API ์คํ์ ๋ฐ๋ผ:
- JWE: RSA1_5 + A128GCM
- JWS: RS256
MIT
- Samsung Wallet Developer Portal
- Samsung Wallet Partner Portal
- Wallet Card Specs
- CDATA Generation (๊ณต์ Java ์ฝ๋)