PayPay OPAでは、前日の取引データなどが、突合ファイルとして生成され、HTTP GETで取得することができます。
こちらのPayPayのディベロッパサイトに、sampleの突合ファイルがありますので、取得して読み込んでみました。
Web Cashier - PayPay Open Payment API Documentation
読み込みに使用した突合ファイル: transaction_000000000000008181_20200130000000_20200130235959.csv
決済番号,加盟店ID,屋号,店舗ID,店舗名,端末番号/PosID,取引ステータス,取引日時,取引金額,レシート番号,支払い方法,マーチャント決済ID
00000000000000000001,000000000000008181,テスト加盟店,test01,テスト01,00001,取引完了,"2020-01-30 23:58:30",150,000-0001,PayPay残高,0001-001
00000000000000000002,000000000000008181,テスト加盟店,test01,テスト01,00001,返金完了,"2020-01-30 23:55:14",-300,000-0002,PayPay残高,0001-002
00000000000000000003,000000000000008181,テスト加盟店,test01,テスト01,00001,取引完了,"2020-01-30 23:49:54",100,000-0003,PayPay残高,0001-003
00000000000000000004,000000000000008181,テスト加盟店,test01,テスト01,00001,取引完了,"2020-01-30 23:47:09",100,000-0004,PayPay残高,0001-004
00000000000000000005,000000000000008181,テスト加盟店,test01,テスト01,00001,取引完了,"2020-01-30 23:45:11",200,000-0005,PayPay残高,0001-005
突合ファイルは、CSV形式だということで、OSSライブラリで読み込みたいと思います。
opencsv –
CSVのライブラリは、いくつかありますが、多くの導入実績があり、
今後も開発が継続していく可能性が高そうな、OpenCSVをチョイスしました。
OpenCSVは、直接、CSVファイルのレコードとJava Beanクラスをバインディングする機能があります。
クラスのフィールド変数にアノテーションを付与して、カラムとの対応付けを定義します。
/**
* 突合ファイルのレコードクラス.
*/
public class ReconciliationRecord implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 決済番号(paymentId).
*/
@CsvBindByName(column = "決済番号")
private String orderId;
/**
* 加盟店ID.
*/
@CsvBindByName(column = "加盟店ID")
private String merchantId;
/**
* 屋号.
*/
@CsvBindByName(column = "屋号")
private String brandName;
/**
* 店舗ID.
*/
@CsvBindByName(column = "店舗ID")
private String storeId;
/**
* 店舗名.
*/
@CsvBindByName(column = "店舗名")
private String storeName;
/**
* 端末番号/PosID.
*/
@CsvBindByName(column = "端末番号/PosID")
private String terminalId;
/**
* 取引ステータス.
*/
@CsvCustomBindByName(column = "取引ステータス", converter = StatusConverter.class)
private Status transactionStatus;
/**
* 取引日時.
*/
@CsvBindByName(column = "取引日時")
@CsvDate("yyyy-MM-dd HH:mm:ss")
private Date acceptedAt;
/**
* 取引金額.
*/
@CsvBindByName(column = "取引金額")
private Long amount;
/**
* レシート番号.
*/
@CsvBindByName(column = "レシート番号")
private String orderReceiptNumber;
/**
* 支払い方法.
*/
@CsvBindByName(column = "支払い方法")
private String methodOfPayment;
/**
* マーチャント決済ID.
*/
@CsvBindByName(column = "マーチャント決済ID")
private String merchantPaymentId;
public ReconciliationRecord() {
}
@Override
public String toString() {
return new StringJoiner(", ", ReconciliationRecord.class.getSimpleName() + "[", "]")
.add("orderId='" + orderId + "'")
.add("merchantId='" + merchantId + "'")
.add("brandName='" + brandName + "'")
.add("storeId='" + storeId + "'")
.add("storeName='" + storeName + "'")
.add("terminalId='" + terminalId + "'")
.add("transactionStatus=" + transactionStatus)
.add("acceptedAt=" + acceptedAt)
.add("amount=" + amount)
.add("orderReceiptNumber='" + orderReceiptNumber + "'")
.add("methodOfPayment='" + methodOfPayment + "'")
.add("merchantPaymentId='" + merchantPaymentId + "'")
.toString();
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public String getMerchantId() {
return merchantId;
}
public void setMerchantId(String merchantId) {
this.merchantId = merchantId;
}
public String getBrandName() {
return brandName;
}
public void setBrandName(String brandName) {
this.brandName = brandName;
}
public String getStoreId() {
return storeId;
}
public void setStoreId(String storeId) {
this.storeId = storeId;
}
public String getStoreName() {
return storeName;
}
public void setStoreName(String storeName) {
this.storeName = storeName;
}
public String getTerminalId() {
return terminalId;
}
public void setTerminalId(String terminalId) {
this.terminalId = terminalId;
}
public Status getTransactionStatus() {
return transactionStatus;
}
public void setTransactionStatus(Status transactionStatus) {
this.transactionStatus = transactionStatus;
}
public Date getAcceptedAt() {
return acceptedAt;
}
public void setAcceptedAt(Date acceptedAt) {
this.acceptedAt = acceptedAt;
}
public Long getAmount() {
return amount;
}
public void setAmount(Long amount) {
this.amount = amount;
}
public String getOrderReceiptNumber() {
return orderReceiptNumber;
}
public void setOrderReceiptNumber(String orderReceiptNumber) {
this.orderReceiptNumber = orderReceiptNumber;
}
public String getMethodOfPayment() {
return methodOfPayment;
}
public void setMethodOfPayment(String methodOfPayment) {
this.methodOfPayment = methodOfPayment;
}
public String getMerchantPaymentId() {
return merchantPaymentId;
}
public void setMerchantPaymentId(String merchantPaymentId) {
this.merchantPaymentId = merchantPaymentId;
}
enum Status {
COMPLETED, FAILED, REFUND_COMPLETED, REFUND_FAILED, UNKNOWN;
private static final String STRING_COMPLETED = "取引完了";
private static final String STRING_FAILED = "取引失敗";
private static final String STRING_REFUND_COMPLETED = "返金完了";
private static final String STRING_REFUND_FAILED = "返金失敗";
static Status statusOf(final String value) {
Status status = null;
switch (value) {
case STRING_COMPLETED:
status = COMPLETED;
break;
case STRING_FAILED:
status = FAILED;
break;
case STRING_REFUND_COMPLETED:
status = REFUND_COMPLETED;
break;
case STRING_REFUND_FAILED:
status = REFUND_FAILED;
break;
default:
status = UNKNOWN;
break;
}
return status;
}
}
}
固定値をenum型に変換したかったので、OpenCSVのAbstractBeanFieldクラスを継承して変換処理を実装しています。
そして、この変換処理を適用したいフィールド変数に対して、
@CsvCustomBindByName(column = "取引ステータス", converter = StatusConverter.class)というように指定します。
/**
* 取引ステータスをString<->enumに変換します.
*/
public class StatusConverter extends AbstractBeanField {
public StatusConverter() {
super();
}
@Override
protected ReconciliationRecord.Status convert(String value)
throws CsvDataTypeMismatchException, CsvConstraintViolationException {
return ReconciliationRecord.Status.statusOf(value);
}
}
特に、PayPayのサイトには、記載されてないが、ファイルのエンコーディングは、SHIFT_JISっぽいですね。
csvToBeanクラスを生成して、突合ファイルを読み込みしてみます。
File file = new File("/PATH/TO/transaction_000000000000008181_20200130000000_20200130235959.csv");
try (Reader reader = new InputStreamReader(new FileInputStream(file), "SHIFT_JIS")) {
CsvToBean<ReconciliationRecord> csvToBean = new CsvToBeanBuilder(reader).withType(ReconciliationRecord.class).build();
csvToBean.stream().parallel().forEach(new Consumer<ReconciliationRecord>() {
@Override
public void accept(ReconciliationRecord reconciliationRecord) {
log.debug(reconciliationRecord);
}
});
}
結果
ReconciliationRecord[orderId='00000000000000000001', merchantId='000000000000008181', brandName='テスト加盟店', storeId='test01', storeName='テスト01', terminalId='00001', transactionStatus=COMPLETED, acceptedAt=Thu Jan 30 23:58:30 JST 2020, amount=150, orderReceiptNumber='000-0001', methodOfPayment='PayPay残高', merchantPaymentId='0001-001']
ReconciliationRecord[orderId='00000000000000000002', merchantId='000000000000008181', brandName='テスト加盟店', storeId='test01', storeName='テスト01', terminalId='00001', transactionStatus=REFUND_COMPLETED, acceptedAt=Thu Jan 30 23:55:14 JST 2020, amount=-300, orderReceiptNumber='000-0002', methodOfPayment='PayPay残高', merchantPaymentId='0001-002']
ReconciliationRecord[orderId='00000000000000000003', merchantId='000000000000008181', brandName='テスト加盟店', storeId='test01', storeName='テスト01', terminalId='00001', transactionStatus=COMPLETED, acceptedAt=Thu Jan 30 23:49:54 JST 2020, amount=100, orderReceiptNumber='000-0003', methodOfPayment='PayPay残高', merchantPaymentId='0001-003']
ReconciliationRecord[orderId='00000000000000000004', merchantId='000000000000008181', brandName='テスト加盟店', storeId='test01', storeName='テスト01', terminalId='00001', transactionStatus=COMPLETED, acceptedAt=Thu Jan 30 23:47:09 JST 2020, amount=100, orderReceiptNumber='000-0004', methodOfPayment='PayPay残高', merchantPaymentId='0001-004']
ReconciliationRecord[orderId='00000000000000000005', merchantId='000000000000008181', brandName='テスト加盟店', storeId='test01', storeName='テスト01', terminalId='00001', transactionStatus=COMPLETED, acceptedAt=Thu Jan 30 23:45:11 JST 2020, amount=200, orderReceiptNumber='000-0005', methodOfPayment='PayPay残高', merchantPaymentId='0001-005']
ふと、これを見て、acceptedAtは、タイムゾーンまで含んでいない。
JSTであってるだろうかという疑問がでてきました。
他の日時パラメタは、エポックタイムスタンプだったり、協定世界時 (UTC)なのに何故でしょうか。UTCかもしれないと思いつつ、
ファイルのエンコーディングがSHIFT_JISっぽいし、カラム名が日本語だし、JSTの可能性も否めない。
そこで、別の種類の突合ファイルをPayPayのディベロッパサイトからダウンロードして見てみたところ。
preauth_transaction_000000000000008181_20200130000000_20200130235959.csv
"orderId","merchantId","brandName","storeId","storeName","terminalId","transactionStatus","acceptedAt","amount","orderReceiptNumber","methodOfPayment","merchantPaymentId"
"3456789012345678901","234567890123456789","〇〇加盟店","01234567","","0000","COMPLETED","2020-09-23T00:00:26+09:00","480","","wallet","1447142183_202009230000250894_2"
"3456789012345678902","234567890123456789","〇〇加盟店","01234567","","0000","CANCELED","2020-09-23T07:45:03+09:00","1848","","wallet","1480206255_202009230745030768_6"
"3455979365068570624","234567890123456789","〇〇加盟店","01234567","","0000","EXPIRED","2020-09-23T13:00:00+09:00","1738","","wallet","1000014012_202009302301040213_0"
"3456423834054230016","234567890123456789","〇〇加盟店","01234567","","0000","FAILED","2020-09-23T13:24:54+09:00","20","","wallet","pp_0cb694dc-5340-44ef-a173-6034de590bc5"
"3456789012345678903","234567890123456789","〇〇加盟店","01234567","","0000","REFUNDED","2020-09-23T16:53:25+09:00","1325","","wallet","1000191023_202008261653230283_4"
"3456789012345678904","234567890123456789","〇〇加盟店","01234567","","0000","AUTHORIZED","2020-09-23T23:31:43+09:00","2924","","wallet","1489917170_202009232331310185_0"
こっちは、+09:00がついています。
ファイル名からみると、1/30のデータっぽいけど、レコードは、9/23です。
レコードが、1/30なら確定的だったけど、これは参考にならないかもしれない。
しかしながら、単純に、+09:00を落としただけなんじゃないかという気が、、、確認する必要がありそうです。