@unchainedshop/plugins
Version:
Because of a Typescript issue with upstream "postfinancecheckout", the Postfinance plugin has been disabled from transpilation, import the source ts files from src and enable node_module tsc or copy over the src/payment/postfinance-checkout to your projec
138 lines (121 loc) • 4.82 kB
text/typescript
import { Context } from '@unchainedshop/api';
import { EnrollmentStatus } from '@unchainedshop/core-enrollments';
import { createLogger } from '@unchainedshop/logger';
import { fixPeriods } from './fix-periods.js';
import { FastifyRequest, RouteHandlerMethod } from 'fastify';
import { AppleTransactionsModule } from './module.js';
const logger = createLogger('unchained:core-payment:iap');
const { APPLE_IAP_SHARED_SECRET } = process.env;
const AppleNotificationTypes = {
INITIAL_BUY: 'INITIAL_BUY',
DID_RECOVER: 'DID_RECOVER',
DID_CHANGE_RENEWAL_STATUS: 'DID_CHANGE_RENEWAL_STATUS',
DID_FAIL_TO_RENEW: 'DID_FAIL_TO_RENEW',
DID_CHANGE_RENEWAL_PREF: 'DID_CHANGE_RENEWAL_PREF',
};
export const appleIAPHandler: RouteHandlerMethod = async (
req: FastifyRequest & {
unchainedContext: Context & {
modules: AppleTransactionsModule;
};
},
reply,
) => {
try {
const resolvedContext = req.unchainedContext as Context;
const { modules, services } = resolvedContext;
const responseBody: Record<string, any> = req.body || {};
if (responseBody.password !== APPLE_IAP_SHARED_SECRET) {
throw new Error('shared secret not valid');
}
const transactions = responseBody?.unified_receipt?.latest_receipt_info; // eslint-disable-line
const latestTransaction = transactions[0];
if (responseBody.notification_type === AppleNotificationTypes.INITIAL_BUY) {
// Find the cart to checkout
const orderPayment = await modules.orders.payments.findOrderPaymentByContextData({
context: {
'meta.transactionIdentifier': latestTransaction.transaction_id,
},
});
if (!orderPayment) throw new Error('Could not find any matching order payment');
const order = await services.orders.checkoutOrder(orderPayment.orderId, {
paymentContext: {
receiptData: responseBody?.unified_receipt?.latest_receipt, // eslint-disable-line
},
});
const orderId = order._id;
const enrollment = await modules.enrollments.findEnrollment({
orderId,
});
await fixPeriods(
{
transactionId: latestTransaction.original_transaction_id,
transactions,
enrollmentId: enrollment._id,
orderId,
},
resolvedContext,
);
logger.info(`Apple IAP Webhook: Confirmed checkout for order ${order.orderNumber}`, {
orderId: order._id,
});
} else {
// Just store payment credentials, use the enrollments paymentProvider reference and
// let the job do the rest
const originalOrderPayment = await modules.orders.payments.findOrderPaymentByContextData({
context: {
'meta.transactionIdentifier': latestTransaction.original_transaction_id,
},
});
if (!originalOrderPayment) throw new Error('Could not find any matching order payment');
const originalOrder = await modules.orders.findOrder({
orderId: originalOrderPayment.orderId,
});
const enrollment = await modules.enrollments.findEnrollment({
orderId: originalOrder._id,
});
await services.orders.registerPaymentCredentials(enrollment.payment.paymentProviderId, {
transactionContext: {
receiptData: responseBody?.unified_receipt?.latest_receipt, // eslint-disable-line
},
userId: enrollment.userId,
});
await fixPeriods(
{
transactionId: latestTransaction.original_transaction_id,
transactions,
enrollmentId: enrollment._id,
orderId: originalOrder._id,
},
resolvedContext,
);
logger.info(
`Apple IAP Webhook: Processed notification for ${latestTransaction.original_transaction_id} and type ${responseBody.notification_type}`,
);
if (responseBody.notification_type === AppleNotificationTypes.DID_RECOVER) {
if (
enrollment.status !== EnrollmentStatus.TERMINATED &&
responseBody.auto_renew_status === 'false'
) {
await services.enrollments.terminateEnrollment(enrollment);
}
}
if (responseBody.notification_type === AppleNotificationTypes.DID_CHANGE_RENEWAL_STATUS) {
if (
enrollment.status !== EnrollmentStatus.TERMINATED &&
responseBody.auto_renew_status === 'false'
) {
await services.enrollments.terminateEnrollment(enrollment);
}
}
logger.info(`Apple IAP Webhook: Updated enrollment from Apple`);
}
reply.status(200);
return reply.send();
} catch (e) {
logger.warn(`Apple IAP Webhook: ${e.message}`, e);
reply.status(503);
return reply.send({ name: e.name, code: e.code, message: e.message });
}
};
export default appleIAPHandler;