UNPKG

@unchainedshop/plugins

Version:

Official plugin collection for the Unchained Engine with payment, delivery, and pricing adapters

115 lines (114 loc) 5.12 kB
import { PaymentAdapter, PaymentDirector, PaymentError, } from '@unchainedshop/core'; import { createLogger } from '@unchainedshop/logger'; import {} from "./module.js"; import { verifyReceipt } from "./verify-receipt.js"; const logger = createLogger('unchained:apple-iap'); const { APPLE_IAP_SHARED_SECRET } = process.env; const AppleIAP = { ...PaymentAdapter, key: 'shop.unchained.apple-iap', label: 'Apple In-App-Purchase', version: '1.0.0', initialConfiguration: [], typeSupported: (type) => { return type === 'GENERIC'; }, actions: (params, context) => { const { order, modules } = context; const adapterActions = { ...PaymentAdapter.actions(params, context), configurationError() { if (!APPLE_IAP_SHARED_SECRET) { return PaymentError.INCOMPLETE_CONFIGURATION; } return null; }, isActive() { if (adapterActions.configurationError() === null) return true; return false; }, isPayLaterAllowed() { return false; }, async sign() { throw new Error('Payment signing not supported'); }, async validate() { return true; }, async register(transactionContext) { const { receiptData } = transactionContext; const response = await verifyReceipt({ receiptData, password: APPLE_IAP_SHARED_SECRET, }); const { status, latest_receipt_info: latestReceiptInfo } = response; if (status === 0) { logger.debug('Receipt validated and updated for the user'); const latestTransaction = latestReceiptInfo[latestReceiptInfo.length - 1]; return { token: latestTransaction.web_order_line_item_id, latestReceiptInfo, }; } logger.warn('Receipt invalid', { status: response.status, }); return null; }, async charge(transactionContext) { const { meta, paymentCredentials, receiptData } = transactionContext || {}; const { transactionIdentifier } = meta || {}; if (!transactionIdentifier) { throw new Error('You have to set the transaction id on the order payment'); } if (!order) throw new Error('Order not found'); const receiptResponse = receiptData && (await verifyReceipt({ receiptData, password: APPLE_IAP_SHARED_SECRET, })); if (receiptResponse && receiptResponse.status !== 0) { throw new Error('Receipt invalid'); } const transactions = receiptResponse?.latest_receipt_info || paymentCredentials?.meta?.latestReceiptInfo; const matchedTransaction = transactions?.find((transaction) => transaction?.transaction_id === transactionIdentifier); if (!matchedTransaction) { throw new Error(`Cannot match transaction with identifier ${transactionIdentifier}`); } const orderPositions = await modules.orders.positions.findOrderPositions({ orderId: order._id, }); const items = Object.entries(orderPositions.reduce((acc, item) => { return { ...acc, [item.productId]: (acc[item.productId] || 0) + item.quantity, }; }, {})); if (items.length !== 1) { throw new Error('You can only checkout 1 unique product at once'); } const [[productId, quantity]] = items; const isMatchesTransaction = parseInt(matchedTransaction.quantity, 10) === quantity && matchedTransaction.product_id === productId; if (!isMatchesTransaction) throw new Error('Product in order does not match transaction'); const alreadyProcessedTransaction = await modules.appleTransactions.findTransactionById(transactionIdentifier); if (alreadyProcessedTransaction) throw new Error('Transaction already processed'); await modules.appleTransactions.createTransaction({ _id: transactionIdentifier, matchedTransaction, orderId: order._id, }); return { transactionId: transactionIdentifier, }; }, }; return adapterActions; }, }; PaymentDirector.registerAdapter(AppleIAP);